Merge pull request #1552 from microsoft/softchris-patch-9

docs: modernization - the rest
pull/1566/head
chris 3 months ago committed by GitHub
commit 627f67f409
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -1,47 +1,60 @@
# Introduction to Programming Languages and Modern Developer Tools
Welcome to the exciting world of programming! This lesson will introduce you to the fundamental concepts that power every website, app, and digital experience you use daily. You'll discover what programming languages are, how they work, and why they're the building blocks of our digital world.
Hey there, future developer! 👋 Can I tell you something that still gives me chills every single day? You're about to discover that programming isn't just about computers it's about having actual superpowers to bring your wildest ideas to life!
Programming might seem mysterious at first, but think of it as learning a new language one that lets you communicate with computers and bring your creative ideas to life. Whether you want to build websites, create mobile apps, or automate everyday tasks, understanding programming languages is your first step toward digital creativity and problem-solving.
You know that moment when you're using your favorite app and everything just clicks perfectly? When you tap a button and something absolutely magical happens that makes you go "wow, how did they DO that?" Well, someone just like you probably sitting in their favorite coffee shop at 2 AM with their third espresso wrote the code that created that magic. And here's what's going to blow your mind: by the end of this lesson, you'll not only understand how they did it, but you'll be itching to try it yourself!
In this lesson, you'll explore the essential tools that modern web developers use every day. From code editors that help you write clean, efficient code to browsers that let you test and debug your creations, you'll get hands-on experience with the same professional tools used by developers at top tech companies worldwide.
Look, I totally get it if programming feels intimidating right now. When I first started, I honestly thought you needed to be some kind of math genius or have been coding since you were five years old. But here's what completely changed my perspective: programming is exactly like learning to have conversations in a new language. You start with "hello" and "thank you," then work up to ordering coffee, and before you know it, you're having deep philosophical discussions! Except in this case, you're having conversations with computers, and honestly? They're the most patient conversation partners you'll ever have they never judge your mistakes and they're always excited to try again!
Today, we're going to explore the incredible tools that make modern web development not just possible, but seriously addictive. I'm talking about the exact same editors, browsers, and workflows that developers at Netflix, Spotify, and your favorite indie app studio use every single day. And here's the part that's going to make you do a happy dance: most of these professional-grade, industry-standard tools are completely free!
![Intro Programming](../../sketchnotes/webdev101-programming.png)
> Sketchnote by [Tomomi Imura](https://twitter.com/girlie_mac)
## Pre-Lecture Quiz
[Pre-lecture quiz](https://forms.office.com/r/dru4TE0U9n?origin=lprLink)
## Let's See What You Already Know!
Before we jump into the fun stuff, I'm curious what do you already know about this programming world? And listen, if you're looking at these questions thinking "I literally have zero clue about any of this," that's not just okay, it's perfect! That means you're in exactly the right place. Think of this quiz like stretching before a workout we're just warming up those brain muscles!
[Take the pre-lesson quiz](https://forms.office.com/r/dru4TE0U9n?origin=lprLink)
## What You'll Learn
## The Adventure We're About to Go On Together
In this comprehensive introduction, you'll discover:
Okay, I am genuinely bouncing with excitement about what we're going to explore today! Seriously, I wish I could see your face when some of these concepts click. Here's the incredible journey we're taking together:
- **What programming is and why it matters** Understanding the role of programming in creating digital solutions
- **Types of programming languages and their uses** Exploring the landscape of languages from JavaScript to Python
- **Basic elements of a program** Learning the fundamental building blocks that make code work
- **Modern software and tooling for professional developers** Getting hands-on with the same tools used in the industry
- **What programming actually is (and why it's the coolest thing ever!)** We're going to discover how code is literally the invisible magic powering everything around you, from that alarm that somehow knows it's Monday morning to the algorithm that perfectly curates your Netflix recommendations
- **Programming languages and their amazing personalities** Imagine walking into a party where each person has completely different superpowers and ways of solving problems. That's what the programming language world is like, and you're going to love meeting them!
- **The fundamental building blocks that make digital magic happen** Think of these as the ultimate creative LEGO set. Once you understand how these pieces fit together, you'll realize you can literally build anything your imagination dreams up
- **Professional tools that'll make you feel like you just got handed a wizard's wand** I'm not being dramatic here these tools will genuinely make you feel like you have superpowers, and the best part? They're the same ones the pros use!
> 💡 **Learning Tip**: Don't worry about memorizing everything! Focus on understanding the concepts you'll practice and reinforce these ideas throughout the entire curriculum.
> 💡 **Here's the thing**: Don't even think about trying to memorize everything today! Right now, I just want you to feel that spark of excitement about what's possible. The details will stick naturally as we practice together that's how real learning happens!
> You can take this lesson on [Microsoft Learn](https://docs.microsoft.com/learn/modules/web-development-101/introduction-programming/?WT.mc_id=academic-77807-sagibbon)!
## What is Programming?
## So What Exactly *Is* Programming?
Alright, let's tackle the million-dollar question: what is programming, really?
I'll give you a story that completely changed how I think about this. Last week, I was trying to explain to my mom how to use our new smart TV remote. I caught myself saying things like "Press the red button, but not the big red button, the small red button on the left... no, your other left... okay, now hold it for two seconds, not one, not three..." Sound familiar? 😅
Programming (also known as coding or software development) is the process of creating instructions that tell a computer, smartphone, or any digital device exactly what to do. Think of it like writing a very detailed recipe except instead of making cookies, you're creating websites, games, mobile apps, or even smart home controls.
That's programming! It's the art of giving incredibly detailed, step-by-step instructions to something that's very powerful but needs everything spelled out perfectly. Except instead of explaining to your mom (who can ask "which red button?!"), you're explaining to a computer (which just does exactly what you say, even if what you said isn't quite what you meant).
These instructions are written in special languages called **programming languages**, which act as a bridge between human thinking and computer processing. While computers only understand binary code (1s and 0s), programming languages let us write instructions in a way that's much more readable and logical for humans.
Here's what blew my mind when I first learned this: computers are actually pretty simple at their core. They literally only understand two things 1 and 0, which is basically just "yes" and "no" or "on" and "off." That's it! But here's where it gets magical we don't have to speak in 1s and 0s like we're in The Matrix. That's where **programming languages** come to the rescue. They're like having the world's best translator who takes your perfectly normal human thoughts and converts them into computer language.
Every digital experience you interact with started as someone's code: the social media app you scroll through, the GPS that guides your drive, even the simple calculator on your phone. When you learn to program, you're learning to create these digital solutions that can solve real problems and make life easier for millions of people.
And here's what still gives me actual chills every morning when I wake up: literally *everything* digital in your life started with someone just like you, probably sitting in their pajamas with a cup of coffee, typing code on their laptop. That Instagram filter that makes you look flawless? Someone coded that. The recommendation that led you to your new favorite song? A developer built that algorithm. The app that helps you split dinner bills with friends? Yep, someone thought "this is annoying, I bet I could fix this" and then... they did!
**Quick Research Challenge**: Who is considered to have been the world's first computer programmer? Take a moment to look this up the answer might surprise you!
When you learn to program, you're not just picking up a new skill you're becoming part of this incredible community of problem-solvers who spend their days thinking, "What if I could build something that makes someone's day just a little bit better?" Honestly, is there anything cooler than that?
## Programming Languages
**Fun Fact Hunt**: Here's something super cool to look up when you have a spare moment who do you think was the world's first computer programmer? I'll give you a hint: it might not be who you're expecting! The story behind this person is absolutely fascinating and shows that programming has always been about creative problem-solving and thinking outside the box.
Just as humans speak different languages like English, Spanish, or Mandarin, computers can understand different programming languages. Each programming language has its own syntax (grammar rules) and is designed for specific types of tasks, making some languages better suited for certain jobs than others.
## Programming Languages Are Like Different Flavors of Magic
Programming languages serve as translators between human ideas and computer actions. They allow developers to write instructions that are both human-readable and computer-executable. When you write code in a programming language, special software converts your instructions into the binary code that computers actually understand.
Okay, this is going to sound weird, but stick with me programming languages are a lot like different types of music. Think about it: you've got jazz, which is smooth and improvisational, rock that's powerful and straightforward, classical that's elegant and structured, and hip-hop that's creative and expressive. Each style has its own vibe, its own community of passionate fans, and each one is perfect for different moods and occasions.
Programming languages work exactly the same way! You wouldn't use the same language to build a fun mobile game that you'd use to crunch massive amounts of climate data, just like you wouldn't play death metal at a yoga class (well, most yoga classes anyway! 😄).
But here's what absolutely blows my mind every time I think about it: these languages are like having the most patient, brilliant interpreter in the world sitting right next to you. You can express your ideas in a way that feels natural to your human brain, and they handle all the incredibly complex work of translating that into the 1s and 0s that computers actually speak. It's like having a friend who's perfectly fluent in both "human creativity" and "computer logic" and they never get tired, never need coffee breaks, and never judge you for asking the same question twice!
### Popular Programming Languages and Their Uses
@ -55,17 +68,28 @@ Programming languages serve as translators between human ideas and computer acti
### High-Level vs. Low-Level Languages
Programming languages exist on a spectrum from **low-level** (closer to machine code) to **high-level** (closer to human language):
Okay, this was honestly the concept that broke my brain when I first started learning, so I'm going to share the analogy that finally made it click for me and I really hope it helps you too!
Imagine you're visiting a country where you don't speak the language, and you desperately need to find the nearest bathroom (we've all been there, right? 😅):
- **Low-level programming** is like learning the local dialect so well that you can chat with the grandmother selling fruit on the corner using cultural references, local slang, and inside jokes that only someone who grew up there would understand. Super impressive and incredibly efficient... if you happen to be fluent! But pretty overwhelming when you're just trying to find a bathroom.
- **Low-level languages** (like Assembly or C) require fewer translation steps but are harder for humans to read and write
- **High-level languages** (like JavaScript, Python, or C#) are more readable and have larger communities, making them ideal for most modern development
- **High-level programming** is like having that amazing local friend who just gets you. You can say "I really need to find a restroom" in plain English, and they handle all the cultural translation and give you directions in a way that makes perfect sense to your non-local brain.
> 💡 **Think of it this way**: Low-level languages are like speaking directly to the computer in its native dialect, while high-level languages are like having a skilled interpreter who translates your everyday language into computer-speak.
In programming terms:
- **Low-level languages** (like Assembly or C) let you have incredibly detailed conversations with the computer's actual hardware, but you need to think like a machine, which is... well, let's just say it's a pretty big mental shift!
- **High-level languages** (like JavaScript, Python, or C#) let you think like a human while they handle all the machine-speak behind the scenes. Plus, they have these incredibly welcoming communities full of people who remember what it was like to be new and genuinely want to help!
Guess which ones I'm going to suggest you start with? 😉 High-level languages are like having training wheels that you never actually want to take off because they make the whole experience so much more enjoyable!
### Comparing Programming Languages in Action
To illustrate the difference between high-level and low-level languages, let's look at the same task written in two different ways. Both code examples below generate the famous Fibonacci sequence (where each number is the sum of the two preceding ones: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34...).
### Let Me Show You Why High-Level Languages Are So Much Friendlier
Alright, I'm about to show you something that perfectly demonstrates why I fell in love with high-level languages, but first I need you to promise me something. When you see that first code example, don't panic! It's supposed to look intimidating. That's exactly the point I'm making!
We're going to look at the exact same task written in two completely different styles. Both create what's called the Fibonacci sequence it's this beautiful mathematical pattern where each number is the sum of the two before it: 0, 1, 1, 2, 3, 5, 8, 13... (Fun fact: you'll find this pattern literally everywhere in nature sunflower seed spirals, pinecone patterns, even the way galaxies form!)
Ready to see the difference? Let's go!
**High-level language (JavaScript) Human-friendly:**
@ -160,16 +184,20 @@ Notice how the JavaScript version reads almost like English instructions, while
- **Structure**: JavaScript's logical flow matches how humans think about problems step-by-step
- **Maintenance**: Updating the JavaScript version for different requirements is straightforward and clear
**A Fibonacci sequence** is [defined](https://en.wikipedia.org/wiki/Fibonacci_number) as a set of numbers where each number is the sum of the two preceding ones, starting from 0 and 1. This mathematical pattern appears frequently in nature, from flower petals to spiral shells!
**About the Fibonacci sequence**: This absolutely gorgeous number pattern (where each number equals the sum of the two before it: 0, 1, 1, 2, 3, 5, 8...) shows up literally *everywhere* in nature! You'll find it in sunflower spirals, pinecone patterns, the way nautilus shells curve, and even in how tree branches grow. It's pretty mind-blowing how math and code can help us understand and recreate the patterns that nature uses to create beauty!
## The Building Blocks That Make the Magic Happen
Alright, now that you've seen what programming languages look like in action, let's break down the fundamental pieces that make up literally every program ever written. Think of these as the essential ingredients in your favorite recipe once you understand what each one does, you'll be able to read and write code in pretty much any language!
## Elements of a Program
This is kind of like learning the grammar of programming. Remember back in school when you learned about nouns, verbs, and how to put sentences together? Programming has its own version of grammar, and honestly, it's way more logical and forgiving than English grammar ever was! 😄
Now that you understand what programming languages are, let's explore the fundamental building blocks that make up any program. Think of these elements as the grammar and vocabulary of programming once you understand these concepts, you'll be able to read and write code in any language.
### Statements: The Step-by-Step Instructions
### Statements: The Basic Instructions
Let's start with **statements** these are like individual sentences in a conversation with your computer. Each statement tells the computer to do one specific thing, kind of like giving directions: "Turn left here," "Stop at the red light," "Park in that spot."
A **statement** is a single instruction in a program, like a sentence in human language. Each statement tells the computer to perform one specific action. Just as sentences end with periods, statements have specific ways to indicate where one instruction ends and the next begins (this varies by programming language).
What I love about statements is how readable they usually are. Check this out:
```javascript
// Basic statements that perform single actions
@ -193,9 +221,15 @@ document.body.style.backgroundColor = "lightblue";
- **Modify** the webpage's title that appears in the browser tab
- **Change** the background color of the entire page body
### Variables: Storing Information
### Variables: Your Program's Memory System
**Variables** are like labeled containers that hold information your program needs to remember. Just as you might write a grocery list on paper and refer back to it, variables let programs store data and use it later. Variables have unique names and their contents can change as the program runs.
Okay, **variables** are honestly one of my absolute favorite concepts to teach because they're so much like things you already use every single day!
Think about your phone's contact list for a second. You don't memorize everyone's phone number instead, you save "Mom," "Best Friend," or "Pizza Place That Delivers Until 2 AM" and let your phone remember the actual numbers. Variables work exactly the same way! They're like labeled containers where your program can store information and retrieve it later using a name that actually makes sense.
Here's what's really cool: variables can change as your program runs (hence the name "variable" see what they did there?). Just like you might update that pizza place contact when you discover somewhere even better, variables can be updated as your program learns new information or as situations change!
Let me show you how beautifully simple this can be:
```javascript
// Step 1: Creating basic variables
@ -252,9 +286,15 @@ console.log(`${location} humidity: ${humidity}%`);
- **Create** new variables automatically with the same names as object keys
- **Simplify** code by avoiding repetitive dot notation
### Control Flow: Making Decisions
### Control Flow: Teaching Your Program to Think
Okay, this is where programming gets absolutely mind-blowing! **Control flow** is basically teaching your program how to make smart decisions, exactly like you do every single day without even thinking about it.
Programs often need to make decisions based on different situations. **Control flow statements** (like `if...else`) allow programs to choose different paths, making them smart and responsive to changing conditions.
Picture this: this morning you probably went through something like "If it's raining, I'll grab an umbrella. If it's cold, I'll wear a jacket. If I'm running late, I'll skip breakfast and grab coffee on the way." Your brain naturally follows this if-then logic dozens of times every day!
This is what makes programs feel intelligent and alive instead of just following some boring, predictable script. They can actually look at a situation, evaluate what's happening, and respond appropriately. It's like giving your program a brain that can adapt and make choices!
Want to see how beautifully this works? Let me show you:
```javascript
// Step 1: Basic conditional logic
@ -333,25 +373,31 @@ switch (dayOfWeek) {
- **Include** a `default` case to handle unexpected values
- **Use** `break` statements to prevent code from continuing to the next case
> 💡 **Real-world analogy**: Think of control flow like a GPS giving you directions. It might say "If there's traffic on Main Street, take the highway instead." Programs use the same type of conditional logic to respond to different situations.
> 💡 **Real-world analogy**: Think of control flow like having the world's most patient GPS giving you directions. It might say "If there's traffic on Main Street, take the highway instead. If construction is blocking the highway, try the scenic route." Programs use exactly the same type of conditional logic to respond intelligently to different situations and always give users the best possible experience.
**Coming up**: You'll dive deeper into these concepts and learn how to use them effectively in the upcoming lessons. Don't worry about memorizing everything now focus on understanding the big picture!
**What's coming up next**: We're going to have an absolute blast diving deeper into these concepts as we continue this incredible journey together! Right now, just focus on feeling that excitement about all the amazing possibilities ahead of you. The specific skills and techniques will stick naturally as we practice together I promise this is going to be so much more fun than you might expect!
## Tools of the Trade
Just as a carpenter needs quality tools to build beautiful furniture, web developers rely on specialized software and workflows to create amazing digital experiences. In this section, you'll discover the essential tools that professional developers use every day and the best part is, many of these powerful tools are completely free!
Alright, this is honestly where I get so excited I can barely contain myself! 🚀 We're about to talk about the incredible tools that are going to make you feel like you just got handed the keys to a digital spaceship.
You know how a chef has those perfectly balanced knives that feel like extensions of their hands? Or how a musician has that one guitar that seems to sing the moment they touch it? Well, developers have our own version of these magical tools, and here's what's going to absolutely blow your mind most of them are completely free!
I'm practically bouncing in my chair thinking about sharing these with you because they've completely revolutionized how we build software. We're talking about AI-powered coding assistants that can help write your code (I'm not even kidding!), cloud environments where you can build entire applications from literally anywhere with Wi-Fi, and debugging tools so sophisticated they're like having X-ray vision for your programs.
And here's the part that still gives me chills: these aren't "beginner tools" that you'll outgrow. These are the exact same professional-grade tools that developers at Google, Netflix, and that indie app studio you love are using right this very moment. You're going to feel like such a pro using them!
The modern web development landscape has been transformed by innovative tools like AI-powered code assistants (such as GitHub Copilot), cloud-based development environments, and sophisticated debugging tools. These technologies have made it easier than ever to learn programming and build professional-quality applications.
As you progress in your web development journey, you'll discover that having the right tools can dramatically improve your productivity, help you catch errors before they become problems, and make coding more enjoyable and efficient.
### Code Editors and IDEs: Your New Digital Best Friends
Let's talk about code editors these are seriously about to become your new favorite places to hang out! Think of them as your personal coding sanctuary where you'll spend most of your time crafting and perfecting your digital creations.
### Code Editors and IDEs: Your Digital Workshop
But here's what's absolutely magical about modern editors: they're not just fancy text editors. They're like having the most brilliant, supportive coding mentor sitting right next to you 24/7. They catch your typos before you even notice them, suggest improvements that make you look like a genius, help you understand what every piece of code does, and some of them can even predict what you're about to type and offer to finish your thoughts!
Think of a code editor as your digital workshop it's where you'll spend most of your time crafting, testing, and perfecting your code. Modern editors and Integrated Development Environments (IDEs) are far more than simple text editors; they're intelligent assistants that help you write better code faster.
I remember when I first discovered auto-completion I literally felt like I was living in the future. You start typing something, and your editor goes, "Hey, were you thinking of this function that does exactly what you need?" It's like having a mind reader as your coding buddy!
**What makes modern editors so powerful?**
**What makes these editors so incredible?**
Modern code editors offer an impressive array of features designed to boost your productivity:
@ -390,11 +436,15 @@ Modern code editors offer an impressive array of features designed to boost your
> 💡 **Getting Started Tip**: Start with Visual Studio Code it's free, widely used in the industry, and has an enormous community creating helpful tutorials and extensions.
### Web Browsers: Your Testing Laboratory
### Web Browsers: Your Secret Development Laboratory
Web browsers are much more than tools for browsing the internet they're sophisticated development environments that help you build, test, and optimize web applications. Every modern browser includes powerful developer tools (DevTools) that provide deep insights into how your code performs.
Okay, prepare to have your mind completely blown! You know how you've been using browsers to scroll through social media and watch videos? Well, it turns out they've been hiding this incredible secret developer laboratory this entire time, just waiting for you to discover it!
**Why browsers are essential for web development:**
Every single time you right-click on a webpage and select "Inspect Element," you're opening up a hidden world of developer tools that are honestly more powerful than some expensive software I used to pay hundreds of dollars for. It's like discovering that your regular old kitchen has been concealing a professional chef's laboratory behind a secret panel!
The first time someone showed me browser DevTools, I spent like three hours just clicking around and going "WAIT, IT CAN DO THAT TOO?!" You can literally edit any website in real-time, see exactly how fast everything loads, test how your site looks on different devices, and even debug JavaScript like a total pro. It's absolutely mind-blowing!
**Here's why browsers are your secret weapon:**
When you create a website or web application, you need to see how it looks and behaves in the real world. Browsers not only display your work but also provide detailed feedback about performance, accessibility, and potential issues.
@ -419,11 +469,15 @@ Modern browsers include comprehensive development suites:
> ⚠️ **Important Testing Tip**: Always test your websites in multiple browsers! What works perfectly in Chrome might look different in Safari or Firefox. Professional developers test across all major browsers to ensure consistent user experiences.
### Command Line Tools: The Power User's Gateway
### Command Line Tools: Your Gateway to Developer Superpowers
Alright, let's have a completely honest moment here about the command line, because I want you to hear this from someone who truly gets it. When I first saw it just this scary black screen with blinking text I literally thought, "Nope, absolutely not! This looks like something from a 1980s hacker movie, and I am definitely not smart enough for this!" 😅
But here's what I wish someone had told me back then, and what I'm telling you right now: the command line isn't scary it's actually like having a direct conversation with your computer. Think of it like the difference between ordering food through a fancy app with pictures and menus (which is nice and easy) versus walking into your favorite local restaurant where the chef knows exactly what you like and can whip up something perfect just by you saying "surprise me with something amazing."
The command line (also called terminal or shell) might look intimidating at first it's just a black screen with text! But don't let its simple appearance fool you. The command line is one of the most powerful tools in a developer's toolkit, allowing you to perform complex tasks with simple text commands.
The command line is where developers go to feel like absolute wizards. You type a few seemingly magical words (okay, they're just commands, but they feel magical!), hit enter, and BOOM you've created entire project structures, installed powerful tools from around the world, or deployed your app to the internet for millions of people to see. Once you get your first taste of that power, it's honestly pretty addictive!
**Why developers love the command line:**
**Why the command line will become your favorite tool:**
While graphical interfaces are great for many tasks, the command line excels at automation, precision, and speed. Many development tools work primarily through command line interfaces, and learning to use them efficiently can dramatically improve your productivity.
@ -496,11 +550,15 @@ npx vite
> 🎯 **Learning Path**: Start with basic commands like `cd` (change directory), `ls` or `dir` (list files), and `mkdir` (create folder). Practice with modern workflow commands like `npm install`, `git status`, and `code .` (opens current directory in VS Code). As you become more comfortable, you'll naturally pick up more advanced commands and automation techniques.
### Documentation: Your Learning Compass
### Documentation: Your Always-Available Learning Mentor
Documentation is like having a knowledgeable mentor available 24/7. When you encounter new concepts, need to understand how a tool works, or want to explore advanced features, high-quality documentation becomes your roadmap to success.
Okay, let me share a little secret that's going to make you feel so much better about being a beginner: even the most experienced developers spend a huge chunk of their time reading documentation. And that's not because they don't know what they're doing it's actually a sign of wisdom!
**Why documentation matters:**
Think of documentation as having access to the world's most patient, knowledgeable teachers who are available 24/7. Stuck on a problem at 2 AM? Documentation is there with a warm virtual hug and exactly the answer you need. Want to learn about some cool new feature that everyone's talking about? Documentation has your back with step-by-step examples. Trying to understand why something works the way it does? You guessed it documentation is ready to explain it in a way that finally makes it click!
Here's something that completely changed my perspective: the web development world moves incredibly fast, and nobody (I mean absolutely nobody!) keeps everything memorized. I've watched senior developers with 15+ years of experience look up basic syntax, and you know what? That's not embarrassing that's smart! It's not about having a perfect memory; it's about knowing where to find reliable answers quickly and understanding how to apply them.
**Here's where the real magic happens:**
Professional developers spend a significant portion of their time reading documentation not because they don't know what they're doing, but because the web development landscape evolves so rapidly that staying current requires continuous learning. Great documentation helps you understand not just *how* to use something, but *why* and *when* to use it.
@ -530,7 +588,7 @@ Professional developers spend a significant portion of their time reading docume
> 📚 **Study Strategy**: Don't try to memorize documentation instead, learn how to navigate it efficiently. Bookmark frequently-used references and practice using the search functions to find specific information quickly.
**Reflection Exercise**: Think about how web development tools might differ from web design tools. Consider how accessibility features, responsive design capabilities, and collaborative workflows might vary between the two roles. This comparison will help you understand the broader ecosystem of web creation!
**Food for thought**: Here's something interesting to ponder how do you think the tools for building websites (development) might be different from tools for designing how they look (design)? It's like the difference between being an architect who designs a beautiful house and the contractor who actually builds it. Both are crucial, but they need different toolboxes! This kind of thinking will really help you see the bigger picture of how websites come to life.
## GitHub Copilot Agent Challenge 🚀
@ -545,45 +603,53 @@ Use the Agent mode to complete the following challenge:
## 🚀 Challenge
**Explore Programming Language Diversity**
**Alright, detective, ready for your first case?**
Now that you've got this awesome foundation, I've got an adventure that's going to help you see just how incredibly diverse and fascinating the programming world really is. And listen this isn't about writing code yet, so no pressure there! Think of yourself as a programming language detective on your very first exciting case!
**Your mission, should you choose to accept it:**
1. **Become a language explorer**: Pick three programming languages from completely different universes maybe one that builds websites, one that creates mobile apps, and one that crunches data for scientists. Find examples of the same simple task written in each language. I promise you're going to be absolutely amazed at how different they can look while doing the exact same thing!
Now that you understand the basics of programming languages, dive deeper into their unique characteristics and use cases. Choose three programming languages from different categories (e.g., web development, mobile development, data science, systems programming) and research their distinctive features.
2. **Uncover their origin stories**: What makes each language special? Here's a cool fact every single programming language was created because someone thought, "You know what? There's got to be a better way to solve this specific problem." Can you figure out what those problems were? Some of these stories are genuinely fascinating!
**Your task:**
1. **Compare syntax styles**: Find the same simple task (like creating a variable or printing "Hello World") written in your chosen languages
2. **Identify unique strengths**: What makes each language special? What problems is it designed to solve?
3. **Explore communities**: Look at the size and activity of each language's developer community
4. **Consider learning paths**: Which language seems most approachable for a beginner, and why?
3. **Meet the communities**: Check out how welcoming and passionate each language's community is. Some have millions of developers sharing knowledge and helping each other, others are smaller but incredibly tight-knit and supportive. You're going to love seeing the different personalities these communities have!
**Bonus challenge**: Try to find examples of major websites, apps, or systems built with each language. You might be surprised by what powers your favorite digital experiences!
4. **Follow your gut feeling**: Which language feels most approachable to you right now? Don't stress about making the "perfect" choice just listen to your instincts! There's honestly no wrong answer here, and you can always explore others later.
> 💡 **Learning tip**: Don't worry about understanding all the syntax details focus on recognizing patterns and differences in how each language approaches problem-solving.
**Bonus detective work**: See if you can discover what major websites or apps are built with each language. I guarantee you'll be shocked to learn what powers Instagram, Netflix, or that mobile game you can't stop playing!
## Post-Lecture Quiz
[Post-lecture quiz](https://ff-quizzes.netlify.app/web/)
> 💡 **Remember**: You're not trying to become an expert in any of these languages today. You're just getting to know the neighborhood before you decide where you want to set up shop. Take your time, have fun with it, and let your curiosity guide you!
## Let's Celebrate What You've Discovered!
Holy moly, you've absorbed so much incredible information today! I'm genuinely excited to see how much of this amazing journey has stuck with you. And remember this isn't a test where you need to get everything perfect. This is more like a celebration of all the cool stuff you've learned about this fascinating world you're about to dive into!
[Take the post-lesson quiz](https://ff-quizzes.netlify.app/web/)
## Review & Self Study
**Deepen Your Understanding**
**Take your time to explore and have fun with it!**
You've covered a lot of ground today, and that's something to be proud of! Now comes the fun part exploring the topics that sparked your curiosity. Remember, this isn't homework it's an adventure!
Take time to explore the concepts you've learned by diving deeper into the tools and languages that interest you most:
**Dive deeper into what excites you:**
**Programming Language Exploration:**
- Visit the official websites of 2-3 programming languages mentioned in this lesson
- Try online coding playgrounds like [CodePen](https://codepen.io/), [JSFiddle](https://jsfiddle.net/), or [Replit](https://replit.com/) to experiment with simple code
- Read about the history and creators of your favorite programming language understanding the "why" behind a language's design helps you use it more effectively
**Get hands-on with programming languages:**
- Visit the official websites of 2-3 languages that caught your attention. Each one has its own personality and story!
- Try some online coding playgrounds like [CodePen](https://codepen.io/), [JSFiddle](https://jsfiddle.net/), or [Replit](https://replit.com/). Don't be afraid to experiment you can't break anything!
- Read about how your favorite language came to be. Seriously, some of these origin stories are fascinating and will help you understand why languages work the way they do.
**Tool Familiarization:**
- Download and install Visual Studio Code (if you haven't already)
- Explore the Extensions marketplace and install a few recommended extensions
- Open your browser's Developer Tools and spend a few minutes exploring the different tabs and features
**Get comfortable with your new tools:**
- Download Visual Studio Code if you haven't already it's free and you're going to love it!
- Spend a few minutes browsing the Extensions marketplace. It's like an app store for your code editor!
- Open up your browser's Developer Tools and just click around. Don't worry about understanding everything just get familiar with what's there.
**Community Connection:**
- Follow developer communities on platforms like [Dev.to](https://dev.to/), [Stack Overflow](https://stackoverflow.com/), or [GitHub](https://github.com/)
- Watch beginner-friendly programming videos on YouTube or coding tutorial platforms
- Consider joining local meetups or online communities focused on web development
**Join the community:**
- Follow some developer communities on [Dev.to](https://dev.to/), [Stack Overflow](https://stackoverflow.com/), or [GitHub](https://github.com/). The programming community is incredibly welcoming to newcomers!
- Watch some beginner-friendly coding videos on YouTube. There are so many great creators out there who remember what it's like to be starting out.
- Consider joining local meetups or online communities. Trust me, developers love helping newcomers!
> 🎯 **Remember**: The goal isn't to master everything immediately, but to build familiarity with the landscape of tools and concepts you'll encounter as you continue learning web development.
> 🎯 **Listen, here's what I want you to remember**: You're not expected to become a coding wizard overnight! Right now, you're just getting to know this amazing new world you're about to be part of. Take your time, enjoy the journey, and remember every single developer you admire was once sitting exactly where you are right now, feeling excited and maybe a little overwhelmed. That's totally normal, and it means you're doing it right!
@ -591,4 +657,4 @@ Take time to explore the concepts you've learned by diving deeper into the tools
[Reading the Docs](assignment.md)
> Note: When selecting tools for your assignment, do not choose editors, browsers, or command line tools already listed above. Focus on tools that are current, widely used, and have strong community or official support.
> 💡 **A little nudge for your assignment**: I'd absolutely love to see you explore some tools we haven't covered yet! Skip the editors, browsers, and command line tools we've already talked about there's this whole incredible universe of amazing development tools out there just waiting to be discovered. Look for ones that are actively maintained and have vibrant, helpful communities (these tend to have the best tutorials and the most supportive people when you inevitably get stuck and need a friendly hand).

@ -1,10 +1,12 @@
# Introduction to GitHub
GitHub is one of the most essential platforms in modern web development, serving as the collaborative backbone for millions of developers worldwide. Think of it as a combination of cloud storage for your code and a social network for programmers it's where developers share their work, collaborate on projects, and contribute to the open-source community that powers much of the internet you use every day.
Hey there, future developer! 👋 Ready to join millions of coders around the world? I'm genuinely excited to introduce you to GitHub think of it as the social media platform for programmers, except instead of sharing photos of your lunch, we're sharing code and building incredible things together!
In this lesson, you'll discover how GitHub transforms the way developers work together. You'll learn to track changes in your code, collaborate with others seamlessly, and even contribute to projects used by millions of people. This isn't just about storing code online it's about joining a global community of developers and learning the fundamental workflows that every professional developer uses.
Here's what absolutely blows my mind: every app on your phone, every website you visit, and most of the tools you'll learn to use were built by teams of developers collaborating on platforms just like GitHub. That music app you love? Someone like you contributed to it. That game you can't put down? Yep, probably built with GitHub collaboration. And now YOU'RE going to learn how to be part of that amazing community!
By the end of this lesson, you'll have your own GitHub repository, understand how to manage code changes with Git, and know how to contribute to open-source projects. These skills will serve as your foundation for collaborating with other developers throughout your web development journey. Let's dive in and unlock the power of collaborative coding!
I know this might feel like a lot at first heck, I remember staring at my first GitHub page thinking "What on earth does any of this mean?" But here's the thing: every single developer started exactly where you are right now. By the end of this lesson, you'll have your very own GitHub repository (think of it as your personal project showcase in the cloud), and you'll know how to save your work, share it with others, and even contribute to projects that millions of people use.
We're going to take this journey together, one step at a time. No rushing, no pressure just you, me, and some really cool tools that are about to become your new best friends!
![Intro to GitHub](../../sketchnotes/webdev101-github.png)
> Sketchnote by [Tomomi Imura](https://twitter.com/girlie_mac)
@ -14,9 +16,9 @@ By the end of this lesson, you'll have your own GitHub repository, understand ho
## Introduction
Before we dive into hands-on GitHub activities, let's establish the foundation you'll need for success. Understanding the key concepts and ensuring your development environment is properly configured will make your GitHub journey much smoother.
Before we dive into the really exciting stuff, let's get your computer ready for some GitHub magic! Think of this like organizing your art supplies before creating a masterpiece having the right tools ready makes everything so much smoother and way more fun.
In this section, we'll cover the essential knowledge and tools that every developer needs when working with GitHub. Don't worry if some concepts seem unfamiliar at first we'll guide you through each step and explain why these tools are so valuable for web developers.
I'm going to walk you through each setup step personally, and I promise it's not nearly as intimidating as it might look at first glance. If something doesn't click right away, that's completely normal! I remember setting up my first development environment and feeling like I was trying to read ancient hieroglyphics. Every single developer has been exactly where you are right now, wondering if they're doing it right. Spoiler alert: if you're here learning, you're already doing it right! 🌟
In this lesson, we'll cover:
@ -26,14 +28,14 @@ In this lesson, we'll cover:
### Prerequisites
Setting up your development environment properly is crucial for a smooth GitHub experience. Think of this as preparing your toolkit before starting a project having the right tools configured correctly will save you time and frustration later.
Let's get your computer ready for some GitHub magic! Don't worry this setup is something you only need to do once, and then you'll be all set for your entire coding journey.
Let's ensure you have everything you need to start collaborating with Git and GitHub effectively.
Alright, let's start with the foundation! First, we need to check if Git is already hanging out on your computer. Git is basically like having a super-smart assistant that remembers every single change you make to your code way better than frantically hitting Ctrl+S every two seconds (we've all been there!).
Before you begin, you'll need to check if Git is installed. In the terminal type:
Let's see if Git is already installed by typing this magic command in your terminal:
`git --version`
If Git is not installed, [download Git](https://git-scm.com/downloads). Then, setup your local Git profile in the terminal:
If Git isn't there yet, no worries! Just head over to [download Git](https://git-scm.com/downloads) and grab it. Once you've got it installed, we need to introduce Git to you properly:
> 💡 **First Time Setup**: These commands tell Git who you are. This information will be attached to every commit you make, so choose a name and email you're comfortable sharing publicly.
@ -59,11 +61,11 @@ Navigate to [github.com](https://github.com/) and create an account if you haven
You'll need both a folder with a code project on your local machine (laptop or PC), and a public repository on GitHub, which will serve as an example for how to contribute to the projects of others.
### Modern Security Practices
### Keeping Your Code Safe
Security in software development isn't just important it's essential. As you begin your journey with GitHub, establishing secure practices from the start will protect your code, your collaborators, and the projects you contribute to.
Let's talk about security for a moment but don't worry, we're not going to overwhelm you with scary stuff! Think of these security practices like locking your car or your house. They're simple habits that become second nature and keep your hard work protected.
Modern development workflows prioritize security at every step. Let's explore the key security practices that every developer should know when working with GitHub and Git.
We'll show you the modern, secure ways to work with GitHub right from the start. This way, you'll develop good habits that will serve you well throughout your coding career.
When working with GitHub, it's important to follow security best practices:
@ -92,63 +94,70 @@ git remote set-url origin git@github.com:username/repository.git
---
## Code management
## Managing Your Code Like a Pro
Okay, THIS is where things get really exciting! 🎉 We're about to learn how to track and manage your code like the pros do, and honestly, this is one of my favorite things to teach because it's such a game-changer.
Now that you understand the importance of GitHub and have your environment set up, let's dive into the practical skills you'll use every day as a developer. Code management with Git is like maintaining a detailed journal of your project's evolution every change, every decision, and every milestone is carefully tracked and preserved.
Picture this: you're writing an amazing story, and you want to keep track of every draft, every brilliant edit, and every "wait, that's genius!" moment along the way. That's exactly what Git does for your code! It's like having the most incredible time-traveling notebook that remembers EVERYTHING every keystroke, every change, every "oops, that broke everything" moment that you can instantly undo.
Think of Git as your coding time machine. You can see exactly what changed, when it changed, and why it changed. This becomes incredibly valuable when working on complex projects or collaborating with others.
I'll be honest this might feel overwhelming at first. When I started, I thought "Why can't I just save my files like normal?" But trust me on this: once Git clicks for you (and it will!), you'll have one of those lightbulb moments where you think "How did I EVER code without this?" It's like discovering you can fly when you've been walking everywhere your whole life!
Let's say you have a folder locally with some code project and you want to start tracking your progress using git - the version control system. Some people compare using git to writing a love letter to your future self. Reading your commit messages days or weeks or months later you'll be able to recall why you made a decision, or "rollback" a change - that is, when you write good "commit messages".
### Task: Make a repository and commit code
### Task: Create Your First Repository!
> 🎯 **Your Mission (and I'm so excited for you!)**: We're going to create your very first GitHub repository together! By the time we're done here, you'll have your own little corner of the internet where your code lives, and you'll have made your first "commit" (that's developer speak for saving your work in a really smart way).
>
> This is honestly such a special moment you're about to officially join the global community of developers! I still remember the thrill of creating my first repo and thinking "Wow, I'm really doing this!"
> 🎯 **Learning Goal**: By the end of this task, you'll have created your first GitHub repository and made your first commit. This is your entry point into the world of version control!
Let's walk through this adventure together, step by step. Take your time with each part there's no prize for rushing, and I promise every single step will make sense. Remember, every coding superstar you admire was once sitting exactly where you are, about to create their first repository. How cool is that?
> Check out video
>
> [![Git and GitHub basics video](https://img.youtube.com/vi/9R31OUPpxU4/0.jpg)](https://www.youtube.com/watch?v=9R31OUPpxU4)
**Step-by-Step Workflow:**
**Let's Do This Together:**
1. **Create your repository on GitHub**. Head over to GitHub.com and look for that bright green **New** button (or the **+** sign in the top right corner). Click it and select **New repository**.
1. **Create repository on GitHub**. On GitHub.com, in the repositories tab, or from the navigation bar top-right, find the **New** button (green button) or the **+** dropdown and select **New repository**.
Here's what to do:
1. Give your repository a name make it something meaningful to you!
1. Add a description if you want (this helps others understand what your project is about)
1. Decide if you want it public (everyone can see it) or private (just for you)
1. I recommend checking the box to add a README file it's like the front page of your project
1. Click **Create repository** and celebrate you just created your first repo! 🎉
1. Give your repository (folder) a name
1. Add a description (optional but recommended)
1. Choose to make it public or private
1. Consider adding a README file, .gitignore, and license
1. Select **Create repository**.
1. **Navigate to your working folder**. In your terminal, switch to the folder (also known as the directory) you want to start tracking. Type:
2. **Navigate to your project folder**. Now let's open up your terminal (don't worry, it's not as scary as it looks!). We need to tell your computer where your project files are. Type this command:
```bash
cd [name of your folder]
```
**Here's what this command does:**
- **Navigate** to your project directory where your code files are located
- **Prepare** the environment for Git initialization and tracking
**What we're doing here:**
- We're basically saying "Hey computer, take me to my project folder"
- This is like opening a specific folder on your desktop, but we're doing it with text commands
- Replace `[name of your folder]` with the actual name of your project folder
1. **Initialize a git repository**. In your project type:
3. **Turn your folder into a Git repository**. This is where the magic happens! Type:
```bash
git init
```
**Step by step, here's what's happening:**
- **Create** a hidden `.git` folder that contains all version control information
- **Transform** your regular folder into a Git repository that can track changes
- **Set up** the foundation for version control in your project
**Here's what just happened (pretty cool stuff!):**
- Git just created a hidden `.git` folder in your project you won't see it, but it's there!
- Your regular folder is now a "repository" that can track every change you make
- Think of it like giving your folder superpowers to remember everything
1. **Check status**. To check the status of your repository type:
4. **Check what's happening**. Let's see what Git thinks about your project right now:
```bash
git status
```
**Understanding the output:**
**Understanding what Git is telling you:**
The output might look something like this:
You might see something that looks like this:
```output
Changes not staged for commit:
@ -159,119 +168,117 @@ Let's say you have a folder locally with some code project and you want to start
modified: file2.txt
```
**What you need to know about this output:**
- **Red text** typically indicates files that have changes but aren't ready for commit
- **Green text** shows files that are staged and ready to be committed
- **Helpful hints** are provided about what commands you can use next
> 💡 **Understanding Git Status**: This command is your best friend! It tells you exactly what Git sees in your project and what actions you can take next.
**Don't panic! Here's what this means:**
- Files in **red** are files that have changes but aren't ready to be saved yet
- Files in **green** (when you see them) are ready to be saved
- Git is being helpful by telling you exactly what you can do next
The `git status` command tells you things like what files are ready to be _saved_ to the repo or have changes that you might want to persist.
> 💡 **Pro tip**: The `git status` command is your best friend! Use it anytime you're confused about what's going on. It's like asking Git "Hey, what's the situation right now?"
1. **Add all files for tracking** (also called staging files):
5. **Get your files ready to save** (this is called "staging"):
```bash
git add .
```
**Here's what this command does:**
- **Stage** all modified and new files in your project directory
- **Prepare** these files to be included in your next commit
- **Mark** files as ready for the permanent snapshot we'll create next
The `git add` command with the `.` argument indicates that all your files & changes are ready for tracking.
**What we just did:**
- We told Git "Hey, I want to include ALL my files in the next save"
- The `.` is like saying "everything in this folder"
- Now your files are "staged" and ready for the next step
1. **Add selected files for tracking** (selective staging):
**Want to be more selective?** You can add just specific files:
```bash
git add [file or folder name]
```
**When to use selective staging:**
- **Organize** related changes into separate commits for cleaner history
- **Include** only the files that belong together logically
- **Create** more meaningful commit messages by grouping related work
**Why might you want to do this?**
- Sometimes you want to save related changes together
- It helps you organize your work into logical chunks
- Makes it easier to understand what changed and when
> 💡 **Pro Tip**: Use selective adding when you want to commit related changes together. This creates more meaningful commit history.
1. **Unstage files** (if you change your mind):
**Changed your mind?** No worries! You can unstage files like this:
```bash
# Unstage all files
# Unstage everything
git reset
# Unstage a particular file
git reset [file or folder name]
# Unstage just one file
git reset [file name]
```
**Understanding unstaging:**
- **Remove** files from the staging area without losing your changes
- **Keep** your modifications but exclude them from the next commit
- **Reorganize** what you want to include in your commit
Don't worry this doesn't delete your work, it just takes files out of the "ready to save" pile.
1. **Persisting your work** (making a commit). At this point you've added the files to a so called _staging area_. A place where Git is tracking your files. To make the change permanent you need to _commit_ the files. To do so you create a _commit_ with the `git commit` command. A _commit_ represents a saving point in the history of your repo. Type the following to create a _commit_:
6. **Save your work permanently** (making your first commit!):
```bash
git commit -m "first commit"
```
**What happens when you commit:**
- **Create** a permanent snapshot of all staged files at this point in time
- **Record** the commit message to explain what changes were made
- **Generate** a unique identifier (hash) for this specific set of changes
- **Add** this snapshot to your project's version history
> 💡 **Commit Message Tips**: Your first commit message can be simple, but for future commits, be descriptive! Good examples: "Add user login functionality" or "Fix navigation menu bug".
**🎉 Congratulations! You just made your first commit!**
**Here's what just happened:**
- Git took a "snapshot" of all your staged files at this exact moment
- Your commit message "first commit" explains what this save point is about
- Git gave this snapshot a unique ID so you can always find it later
- You've officially started tracking your project's history!
This commits all of your files, adding the message "first commit". For future commit messages you will want to be more descriptive to convey what type of change you've made.
> 💡 **Future commit messages**: For your next commits, be more descriptive! Instead of "updated stuff", try "Add contact form to homepage" or "Fix navigation menu bug". Your future self will thank you!
1. **Connect your local Git repo with GitHub**. A Git repo is good on your machine but at some point you want to have backup of your files somewhere and also invite other people to work with you on your repo. One such great place to do so is GitHub. Remember we've already created a repo on GitHub so the only thing we need to do is to connect our local Git repo with GitHub. The command `git remote add` will do just that. Type the following command:
7. **Connect your local project to GitHub**. Right now, your project exists only on your computer. Let's connect it to your GitHub repository so you can share it with the world!
> Note, before you type the command go to your GitHub repo page to find the repository URL. You will use it in the below command. Replace `https://github.com/username/repository_name.git` with your GitHub URL.
First, go to your GitHub repository page and copy the URL. Then come back here and type:
```bash
git remote add origin https://github.com/username/repository_name.git
```
(Replace that URL with your actual repository URL!)
**Step by step, here's what's happening:**
- **Create** a connection named "origin" that points to your GitHub repository
- **Link** your local Git repository with the remote repository on GitHub
- **Enable** the ability to push your local commits to GitHub and pull changes from GitHub
**What we just did:**
- We created a connection between your local project and your GitHub repository
- "Origin" is just a nickname for your GitHub repository it's like adding a contact to your phone
- Now your local Git knows where to send your code when you're ready to share it
💡 **Modern alternative**: You can also use the GitHub CLI to create and connect your repository in one step:
💡 **Easier way**: If you have GitHub CLI installed, you can do this in one command:
```bash
gh repo create my-repo --public --push --source=.
```
This creates a _remote_, or connection, named "origin" pointing at the GitHub repository you created earlier.
1. **Send local files to GitHub**. So far you've created a _connection_ between the local repo and the GitHub repo. Let's send these files to GitHub with the following command `git push`, like so:
8. **Send your code to GitHub** (the big moment!):
```bash
git push -u origin main
```
**Breaking down this command:**
- **Upload** your commits from the "main" branch to GitHub
- **Set** the upstream branch using `-u` to establish a permanent link
- **Enable** simplified future pushes without specifying branch names
**🚀 This is it! You're uploading your code to GitHub!**
**What's happening:**
- Your commits are traveling from your computer to GitHub
- The `-u` flag sets up a permanent connection so future pushes are easier
- "main" is the name of your primary branch (like the main folder)
- After this, you can just type `git push` for future uploads!
This sends your commits in your "main" branch to GitHub. Setting the `upstream` branch with `-u` in the command establishes a link between your local branch and the remote branch, so you can simply use `git push` or `git pull` without specifying the branch name in the future.
💡 **Quick note**: If your branch is called something else (like "master"), use that name instead. You can check with `git branch --show-current`.
💡 **Note**: If your default branch is named differently (like "master"), replace "main" with your actual branch name. You can check your current branch with `git branch --show-current`.
9. **Your new daily coding rhythm** (this is where it gets addictive!):
2. **To add more changes** (daily workflow). If you want to continue making changes and pushing them to GitHub you'll just need to use the following three commands:
From now on, whenever you make changes to your project, you've got this simple three-step dance:
```bash
git add .
git commit -m "type your commit message here"
git commit -m "describe what you changed"
git push
```
**Step by step, here's your daily workflow:**
- **Stage** your modified files with `git add .` (or add specific files)
- **Commit** your changes with a descriptive message about what you've accomplished
- **Push** to GitHub to backup your work and share it with others
**This becomes your coding heartbeat:**
- Make some awesome changes to your code ✨
- Stage them with `git add` ("Hey Git, pay attention to these changes!")
- Save them with `git commit` and a descriptive message (future you will thank you!)
- Share them with the world using `git push` 🚀
- Rinse and repeat seriously, this becomes as natural as breathing!
I love this workflow because it's like having multiple save points in a video game. Made a change you love? Commit it! Want to try something risky? No problem you can always go back to your last commit if things go sideways!
> 💡 **Tip**: You might also want to adopt a `.gitignore` file to prevent files you don't want to track from showing up on GitHub - like that notes file you store in the same folder but has no place on a public repository. You can find templates for `.gitignore` files at [.gitignore templates](https://github.com/github/gitignore) or create one using [gitignore.io](https://www.toptal.com/developers/gitignore).
@ -293,11 +300,15 @@ As in the subject, in the body (optional) also use the imperative, present tense
✅ Take a few minutes to surf around GitHub. Can you find a really great commit message? Can you find a really minimal one? What information do you think is the most important and useful to convey in a commit message?
## Working on projects with others
## Working with Others (The Fun Part!)
Hold onto your hat because THIS is where GitHub becomes absolutely magical! 🪄 You've mastered managing your own code, but now we're diving into my absolute favorite part collaborating with amazing people from all over the world.
Collaboration is where GitHub truly shines. While managing your own code is valuable, the real magic happens when developers work together to build something amazing. GitHub transforms solo coding into a collaborative symphony where multiple developers can contribute simultaneously without stepping on each other's toes.
Picture this: you wake up tomorrow and see that someone in Tokyo improved your code while you were sleeping. Then someone in Berlin fixes a bug you've been stuck on. By afternoon, a developer in São Paulo has added a feature you never even thought of. That's not science fiction that's just Tuesday in the GitHub universe!
In this section, you'll learn how to make your projects welcoming to other developers and how to contribute meaningfully to existing projects. These collaboration skills are what separate hobbyist coders from professional developers.
What gets me really excited is that the collaboration skills you're about to learn? These are the EXACT same workflows that teams at Google, Microsoft, and your favorite startups use every single day. You're not just learning a cool tool you're learning the secret language that makes the entire software world work together.
Seriously, once you experience the rush of having someone merge your first pull request, you'll understand why developers get so passionate about open source. It's like being part of the world's biggest, most creative team project!
> Check out video
>
@ -307,18 +318,20 @@ The main reason for putting things on GitHub was to make it possible to collabor
In your repository, navigate to `Insights > Community` to see how your project compares to recommended community standards.
> 🎯 **Making Your Repository Professional**: A well-documented repository attracts more contributors and shows that you care about code quality.
Want to make your repository look professional and welcoming? Head over to your repository and click on `Insights > Community`. This cool feature shows you how your project compares to what the GitHub community considers "good repository practices."
> 🎯 **Making Your Project Shine**: A well-organized repository with good documentation is like having a clean, welcoming storefront. It tells people you care about your work and makes others want to contribute!
**Essential Repository Elements:**
**Here's what makes a repository awesome:**
| Element | Purpose | Why It Matters |
|---------|---------|----------------|
| **Description** | Brief summary of your project | Helps people understand what your project does at a glance |
| **README** | Detailed project documentation | The first thing people read - make it count! |
| **Contributing Guidelines** | Instructions for contributors | Shows you welcome collaboration and sets clear expectations |
| **Code of Conduct** | Community behavior standards | Creates a welcoming environment for all contributors |
| **License** | Usage permissions | Defines how others can legally use your code |
| **Security Policy** | Vulnerability reporting process | Shows you take security seriously |
| What to Add | Why It's Important | What It Does for You |
|-------------|-------------------|---------------------|
| **Description** | First impression matters! | People know instantly what your project does |
| **README** | Your project's front page | Like a friendly tour guide for new visitors |
| **Contributing Guidelines** | Shows you welcome help | People know exactly how they can help you |
| **Code of Conduct** | Creates a friendly space | Everyone feels welcome to participate |
| **License** | Legal clarity | Others know how they can use your code |
| **Security Policy** | Shows you're responsible | Demonstrates professional practices |
> 💡 **Pro Tip**: GitHub provides templates for all of these files. When creating a new repository, check the boxes to automatically generate these files.
@ -444,11 +457,17 @@ Update your current local working branch with all new commits from the correspon
`git pull`
## How to contribute to open source
## Contributing to Open Source (Your Chance to Make an Impact!)
Are you ready for something that's going to absolutely blow your mind? 🤯 Let's talk about contributing to open source projects and I'm getting goosebumps just thinking about sharing this with you!
This is your chance to become part of something truly extraordinary. Imagine improving the tools that millions of developers use every day, or fixing a bug in an app that your friends love. That's not just a dream that's what open source contribution is all about!
Here's what gives me chills every time I think about it: every single tool you've been learning with your code editor, the frameworks we'll explore, even the browser you're reading this in started with someone exactly like you making their very first contribution. That brilliant developer who built your favorite VS Code extension? They were once a beginner clicking "create pull request" with shaky hands, just like you're about to do.
Contributing to open source projects is one of the most rewarding experiences in web development. It's your opportunity to give back to the community, learn from experienced developers, and make a real impact on software used by thousands or even millions of people.
And here's the most beautiful part: the open source community is like the internet's biggest group hug. Most projects actively look for newcomers and have issues tagged "good first issue" specifically for people like you! Maintainers genuinely get excited when they see new contributors because they remember their own first steps.
The beauty of open source contribution is that everyone started as a beginner. The developers who created the tools you're learning with today were once exactly where you are now. By contributing to open source, you become part of this continuous cycle of learning and sharing that drives innovation in web development.
You're not just learning to code here you're preparing to join a global family of builders who wake up every day thinking "How can we make the digital world a little bit better?" Welcome to the club! 🌟
First, let's find a repository (or **repo**) on GitHub of interest to you and to which you'd like to contribute a change. You will want to copy its contents to your machine.
@ -503,29 +522,40 @@ Projects might also have discussion in forums, mailing lists, or chat channels l
## 🚀 Challenge
Pair with a friend to work on each other's code. Create a project collaboratively, fork code, create branches, and merge changes.
Alright, it's time to put your shiny new GitHub superpowers to the test! 🚀 Here's a challenge that's going to make everything click in the most satisfying way:
Grab a friend (or that family member who's always asking what you're up to with all this "computer stuff") and embark on a collaborative coding adventure together! This is where the real magic happens create a project, let them fork it, make some branches, and merge changes like the pros you're becoming.
I'm not gonna lie you'll probably laugh at some point (especially when you both try to change the same line), maybe scratch your heads in confusion, but you'll definitely have those amazing "aha!" moments that make all the learning worth it. Plus, there's something special about sharing that first successful merge with someone else it's like a tiny celebration of how far you've come!
Don't have a coding buddy yet? No worries at all! The GitHub community is packed with incredibly welcoming people who remember what it was like to be new. Look for repositories with "good first issue" labels they're basically saying "Hey beginners, come learn with us!" How awesome is that?
## Post-Lecture Quiz
[Post-lecture quiz](https://ff-quizzes.netlify.app/web/en/)
## Review & Self Study
## Review & Keep Learning
Read more about [contributing to open source software](https://opensource.guide/how-to-contribute/#how-to-submit-a-contribution).
Whew! 🎉 Look at you you've just conquered GitHub basics like an absolute champion! If your brain feels a little full right now, that's completely normal and honestly a good sign. You've just learned tools that took me weeks to feel comfortable with when I started.
[Git cheatsheet](https://training.github.com/downloads/github-git-cheat-sheet/).
Git and GitHub are incredibly powerful (like, seriously powerful), and every developer I know including the ones who seem like wizards now had to practice and stumble around a bit before it all clicked. The fact that you've made it through this lesson means you're already on your way to mastering some of the most important tools in a developer's toolkit.
Practice, practice, practice. GitHub has great learning paths available via [GitHub Skills](https://skills.github.com):
Here are some absolutely fantastic resources to help you practice and become even more awesome:
- [Contributing to open source software guide](https://opensource.guide/how-to-contribute/#how-to-submit-a-contribution) Your roadmap to making a difference
- [Git cheatsheet](https://training.github.com/downloads/github-git-cheat-sheet/) Keep this handy for quick reference!
And remember: practice makes progress, not perfection! The more you use Git and GitHub, the more natural it becomes. GitHub has created some amazing interactive courses that let you practice in a safe environment:
- [Introduction to GitHub](https://github.com/skills/introduction-to-github)
- [Communicate using Markdown](https://github.com/skills/communicate-using-markdown)
- [GitHub Pages](https://github.com/skills/github-pages)
- [Managing merge conflicts](https://github.com/skills/resolve-merge-conflicts)
**Additional modern resources**:
- [GitHub CLI documentation](https://cli.github.com/manual/)
- [GitHub Codespaces documentation](https://docs.github.com/en/codespaces)
- [GitHub Actions documentation](https://docs.github.com/en/actions)
- [Git best practices](https://www.atlassian.com/git/tutorials/comparing-workflows)
**Feeling adventurous? Check out these modern tools:**
- [GitHub CLI documentation](https://cli.github.com/manual/) For when you want to feel like a command-line wizard
- [GitHub Codespaces documentation](https://docs.github.com/en/codespaces) Code in the cloud!
- [GitHub Actions documentation](https://docs.github.com/en/actions) Automate all the things
- [Git best practices](https://www.atlassian.com/git/tutorials/comparing-workflows) Level up your workflow game
## GitHub Copilot Agent Challenge 🚀
@ -537,10 +567,12 @@ Use the Agent mode to complete the following challenge:
## Assignment
Complete the [Introduction to GitHub](https://github.com/skills/introduction-to-github) course on GitHub Skills.
Your mission, should you choose to accept it: Complete the [Introduction to GitHub](https://github.com/skills/introduction-to-github) course on GitHub Skills. This interactive course will let you practice everything you've learned in a safe, guided environment. Plus, you'll get a cool badge when you finish! 🏅
**Feeling ready for more challenges?**
- Set up SSH authentication for your GitHub account (no more passwords!)
- Try using GitHub CLI for your daily Git operations
- Create a repository with a GitHub Actions workflow
- Explore GitHub Codespaces by opening this very repository in a cloud-based editor
**Optional advanced assignments**:
- Set up SSH authentication for your GitHub account
- Try using GitHub CLI for common operations
- Create a repository with GitHub Actions workflow
- Explore GitHub Codespaces by opening this repository in a codespace
Remember: every expert was once a beginner. You've got this! 💪

@ -10,25 +10,27 @@
>
> \- Sir Timothy Berners-Lee, W3C Director and inventor of the World Wide Web
Web accessibility isn't just a nice-to-have feature—it's a fundamental principle that makes the internet truly universal. When you build accessible websites, you're not just helping people with disabilities; you're creating better experiences for everyone. Think about how curb cuts designed for wheelchairs also help people with strollers, luggage, or bicycles. Accessible web design works the same way.
Here's something that might surprise you: when you build accessible websites, you're not just helping people with disabilities—you're actually making the web better for everyone!
In this lesson, you'll discover how to create websites that work for everyone, regardless of their abilities or the technologies they use to browse the web. You'll learn practical techniques that are built into modern web standards, explore powerful accessibility testing tools, and understand how accessibility enhances usability for all users.
Ever notice those curb cuts at street corners? They were originally designed for wheelchairs, but now they help people with strollers, delivery workers with dollies, travelers with rolling luggage, and cyclists too. That's exactly how accessible web design works—solutions that help one group often end up benefiting everyone. Pretty cool, right?
By the end of this lesson, you'll have the knowledge and tools to make accessibility a natural part of your development process from day one. Let's explore how thoughtful design choices can open up the web to billions of users worldwide.
In this lesson, we're going to explore how to create websites that truly work for everyone, no matter how they browse the web. You'll discover practical techniques that are already built into web standards, get hands-on with testing tools, and see how accessibility makes your sites more usable for all users.
By the end of this lesson, you'll have the confidence to make accessibility a natural part of your development workflow. Ready to explore how thoughtful design choices can open up the web to billions of users? Let's dive in!
> You can take this lesson on [Microsoft Learn](https://docs.microsoft.com/learn/modules/web-development-101/accessibility/?WT.mc_id=academic-77807-sagibbon)!
## Understanding Assistive Technologies
Before we dive into building accessible websites, let's understand how people with different abilities navigate the web. This knowledge will help you make informed decisions about your design and development choices.
Before we jump into coding, let's take a moment to understand how people with different abilities actually experience the web. This isn't just theory—understanding these real-world navigation patterns will make you a much better developer!
Assistive technologies are specialized tools that help people with disabilities interact with digital content. Understanding how these tools work is essential for creating truly accessible web experiences.
Assistive technologies are pretty amazing tools that help people with disabilities interact with websites in ways that might surprise you. Once you get the hang of how these technologies work, creating accessible web experiences becomes way more intuitive. It's like learning to see your code through someone else's eyes.
### Screen readers
[Screen readers](https://en.wikipedia.org/wiki/Screen_reader) are remarkable assistive technologies that convert digital text into speech or braille output. They're primarily used by people with visual impairments, but they also help users with learning disabilities like dyslexia.
[Screen readers](https://en.wikipedia.org/wiki/Screen_reader) are pretty sophisticated pieces of technology that convert digital text into speech or braille output. While they're primarily used by people with visual impairments, they're also super helpful for users with learning disabilities like dyslexia.
Think of a screen reader as a voice narrator for the web. It reads content aloud in a logical order, announces interactive elements like buttons and links, and provides keyboard shortcuts for efficient navigation. However, screen readers can only work effectively if websites are built with proper structure and meaningful content.
I like to think of a screen reader as having a really smart narrator reading a book to you. It reads content aloud in a logical order, announces interactive elements like "button" or "link," and provides keyboard shortcuts for jumping around a page. But here's the thing—screen readers can only work their magic if we build websites with proper structure and meaningful content. That's where you come in as a developer!
**Popular screen readers across platforms:**
- **Windows**: [NVDA](https://www.nvaccess.org/about-nvda/) (free and most popular), [JAWS](https://webaim.org/articles/jaws/), [Narrator](https://support.microsoft.com/windows/complete-guide-to-narrator-e4397a0d-ef4f-b386-d8ae-c172f109bdb1/?WT.mc_id=academic-77807-sagibbon) (built-in)
@ -45,11 +47,11 @@ Screen readers provide multiple navigation methods that make browsing efficient
- **Link lists**: Generate a list of all links for quick access
- **Form controls**: Navigate directly between input fields and buttons
> 💡 **Developer Insight**: 68% of screen reader users navigate primarily by headings ([WebAIM Survey](https://webaim.org/projects/screenreadersurvey9/#finding)). This is why proper heading structure is crucial!
> 💡 **Here's something that blew my mind**: 68% of screen reader users navigate primarily by headings ([WebAIM Survey](https://webaim.org/projects/screenreadersurvey9/#finding)). This means your heading structure is like a roadmap for users—when you get it right, you're literally helping people find their way around your content faster!
### Building your testing workflow
Effective accessibility testing requires both automated tools and manual verification. Here's a systematic approach that catches the most issues:
Here's some good news—effective accessibility testing doesn't have to be overwhelming! You'll want to combine automated tools (they're fantastic at catching obvious issues) with some hands-on testing. Here's a systematic approach that I've found catches the most issues without eating up your entire day:
**Essential manual testing workflow:**
@ -79,9 +81,9 @@ graph TD
### Zoom and magnification tools
Many users rely on magnification to make content readable. This includes people with low vision, older adults, and even users in bright sunlight or with temporary vision issues. Modern zoom technologies have evolved far beyond simple image scaling.
You know how you sometimes pinch to zoom on your phone when text is too small, or squint at your laptop screen in bright sunlight? Many users rely on magnification tools to make content readable every single day. This includes people with low vision, older adults, and anyone who's ever tried to read a website outdoors.
Understanding how zoom works helps you create responsive designs that remain functional and beautiful at any magnification level.
Modern zoom technologies have evolved beyond just making things bigger. Understanding how these tools work will help you create responsive designs that remain functional and attractive at any magnification level.
**Modern browser zoom capabilities:**
- **Page zoom**: Scales all content proportionally (text, images, layout) - this is the preferred method
@ -99,13 +101,13 @@ Understanding how zoom works helps you create responsive designs that remain fun
## Modern Accessibility Testing Tools
Now that you understand how assistive technologies work, let's explore the tools that help you build and test accessible websites. Combining automated testing with manual verification gives you confidence that your sites work for everyone.
Now that you understand how people navigate the web with assistive technologies, let's explore the tools that help you build and test accessible websites.
Modern accessibility testing follows a comprehensive approach: automated tools catch obvious issues, while manual testing ensures real-world usability.
Think of it like this: automated tools are great at catching obvious issues (like missing alt text), while hands-on testing helps you ensure your site feels good to use in the real world. Together, they give you confidence that your sites work for everyone.
### Color contrast testing
Color contrast is one of the most common accessibility issues, but it's also one of the easiest to fix. Good contrast benefits everyone—from users with visual impairments to people viewing screens in bright sunlight.
Here's some good news: color contrast is one of the most common accessibility issues, but it's also one of the easiest to fix. Good contrast benefits everyone—from users with visual impairments to people trying to read their phones at the beach.
**WCAG contrast requirements:**
@ -146,15 +148,15 @@ The most effective accessibility testing combines multiple approaches. No single
## Building Accessibility from the Ground Up
The most effective approach to web accessibility is building it into your foundation from day one. Retrofitting accessibility later is not only more expensive and time-consuming—it often results in subpar user experiences.
The key to accessibility success is building it into your foundation from day one. I know it's tempting to think "I'll add accessibility later," but that's like trying to add a ramp to a house after it's already built. Possible? Yes. Easy? Not really.
Think of accessibility like building a house: it's much easier to include wheelchair accessibility in the initial architectural plans than to add ramps and wider doorways after construction is complete.
Think of accessibility like planning a house—it's much easier to include wheelchair accessibility in your initial architectural plans than to retrofit everything later.
### The POUR principles: Your accessibility foundation
The Web Content Accessibility Guidelines (WCAG) are built around four fundamental principles known as POUR. These principles provide a framework for making content accessible to users with diverse abilities and technologies.
The Web Content Accessibility Guidelines (WCAG) are built around four fundamental principles that spell out POUR. Don't worry—these aren't stuffy academic concepts! They're actually practical guidelines for making content that works for everyone.
Understanding POUR helps you make accessibility decisions that create inclusive experiences for everyone.
Once you get the hang of POUR, making accessibility decisions becomes way more intuitive. It's like having a mental checklist that guides your design choices. Let's break it down:
**🔍 Perceivable**: Information must be presentable in ways users can perceive through their available senses
@ -285,9 +287,9 @@ button:focus:not(:focus-visible) {
### Semantic HTML: The foundation of accessibility
Semantic HTML is like providing a clear roadmap for assistive technologies. When you use the right HTML elements for their intended purpose, you're giving screen readers, keyboards, and other tools the information they need to help users navigate effectively.
Semantic HTML is like giving assistive technologies a GPS system for your website. When you use the right HTML elements for their intended purpose, you're basically providing screen readers, keyboards, and other tools with a detailed roadmap to help users navigate effectively.
Think of semantic HTML as the difference between a well-organized library with clear categories and signs versus a warehouse where books are randomly scattered. Both contain the same information, but only one is truly usable.
Here's an analogy that really clicked for me: semantic HTML is the difference between a well-organized library with clear categories and helpful signs versus a warehouse where books are scattered randomly. Both places have the same books, but which one would you rather try to find something in? Exactly!
**Building blocks of accessible page structure:**
@ -368,10 +370,10 @@ Think of semantic HTML as the difference between a well-organized library with c
### Heading hierarchy: Creating a logical content outline
Headings are the backbone of accessible content structure. Screen reader users rely heavily on headings to understand and navigate your content—it's like providing a detailed table of contents for your page.
Headings are absolutely crucial for accessible content—they're like the spine that holds everything together. Screen reader users rely heavily on headings to understand and navigate your content. Think of it as providing a table of contents for your page.
**The heading hierarchy rule:**
Never skip heading levels. Always progress logically from `<h1>` to `<h2>` to `<h3>`, and so on. Think of headings like an outline structure in a document.
**Here's the golden rule for headings:**
Never skip levels. Always progress logically from `<h1>` to `<h2>` to `<h3>`, and so on. Remember making outlines in school? It's exactly the same principle—you wouldn't jump from "I. Main Point" straight to "C. Sub-sub-point" without a "A. Sub-point" in between, right?
**Perfect heading structure example:**
@ -501,9 +503,9 @@ Beyond the basics of contrast and color, there are sophisticated techniques that
## Crafting Meaningful Link Text
Links are the highways of the web, but poorly written link text creates roadblocks for users with disabilities. Great link text makes your content scannable and navigable for everyone.
Links are basically the highways of the web, but poorly written link text is like having road signs that just say "Place" instead of "Downtown Chicago." Not very helpful, right?
Screen readers can extract all links from a page and present them as a list—imagine if your links appeared as a directory. Would each link make sense without surrounding context?
Here's something that blew my mind when I first learned it: screen readers can extract all the links from a page and show them as one big list. Imagine if someone handed you a directory of every link on your page. Would each one make sense on its own? That's the test your link text needs to pass!
### Understanding link navigation patterns
@ -680,11 +682,11 @@ Sometimes visual design constraints or technical requirements need special solut
## ARIA: Supercharging HTML Accessibility
[Accessible Rich Internet Applications (ARIA)](https://developer.mozilla.org/docs/Web/Accessibility/ARIA) is like a universal translator between your complex web applications and assistive technologies. When HTML alone can't express the full meaning of your interactive components, ARIA fills in the gaps.
[Accessible Rich Internet Applications (ARIA)](https://developer.mozilla.org/docs/Web/Accessibility/ARIA) is like having a universal translator between your complex web applications and assistive technologies. When HTML alone can't express everything your interactive components are doing, ARIA steps in to fill those gaps.
Think of ARIA as adding helpful annotations to your HTML—like stage directions in a play script that help actors understand their roles and relationships.
I like to think of ARIA as adding helpful annotations to your HTML—kind of like stage directions in a play script that help actors understand their roles and relationships.
**The fundamental rule of ARIA**: Use semantic HTML first, ARIA second. ARIA should enhance and clarify, never replace proper HTML structure.
**Here's the most important rule about ARIA**: Always use semantic HTML first, then add ARIA to enhance it. Think of ARIA as seasoning, not the main dish. It should clarify and enhance your HTML structure, never replace it. Get that foundation right first!
### Strategic ARIA implementation
@ -840,9 +842,9 @@ graph TD
## Making Images and Media Accessible
Visual and audio content are essential parts of modern web experiences, but they can create barriers if not implemented thoughtfully. The goal is to ensure that the information and emotional impact of your media reaches every user.
Visual and audio content are essential parts of modern web experiences, but they can create barriers if not implemented thoughtfully. The goal is ensuring that the information and emotional impact of your media reaches every user. Once you get the hang of it, it becomes second nature.
Different types of media content require different accessibility approaches—understanding these distinctions helps you choose the right solution for each situation.
Different types of media need different accessibility approaches. It's like cooking—you wouldn't treat a delicate fish the same way you'd treat a hearty steak. Understanding these distinctions helps you choose the right solution for each situation.
### Strategic image accessibility
@ -917,7 +919,7 @@ Every image on your website serves a purpose. Understanding that purpose helps y
## Keyboard navigation and focus management
Many users rely exclusively on keyboard navigation. Ensuring your site works perfectly with keyboard input is essential for accessibility.
Many users navigate the web entirely with their keyboards. This includes people with motor disabilities, power users who find keyboards faster than mice, and anyone whose mouse has stopped working. Making sure your site works well with keyboard input is essential and often makes your site more efficient for everyone.
### Essential keyboard navigation patterns
@ -1075,7 +1077,9 @@ Forms are critical for user interaction and require special attention to accessi
## Your Accessibility Journey: Key Takeaways
Congratulations! You've just gained the foundational knowledge to create truly inclusive web experiences. Web accessibility isn't just about compliance—it's about recognizing the diverse ways people interact with digital content and designing for that beautiful complexity.
Congratulations! You've just gained the foundational knowledge to create truly inclusive web experiences. This is pretty exciting stuff! Web accessibility isn't just about checking compliance boxes—it's about recognizing the diverse ways people interact with digital content and designing for that amazing complexity.
You're now part of a growing community of developers who understand that great design works for everyone. Welcome to the club!
**🎯 Your accessibility toolkit now includes:**
@ -1097,9 +1101,9 @@ Congratulations! You've just gained the foundational knowledge to create truly i
> 💡 **Remember**: Accessibility constraints often lead to innovative, elegant solutions that benefit everyone. Curb cuts, captions, and voice controls all started as accessibility features and became mainstream improvements.
**The business case is clear**: Accessible websites reach more users, rank better in search engines, have lower maintenance costs, and avoid legal risks. But more importantly, accessible websites embody the best values of the web—openness, inclusivity, and equal access to information.
**The business case is crystal clear**: Accessible websites reach more users, rank better in search engines, have lower maintenance costs, and avoid legal risks. But honestly? The real reason to care about accessibility goes so much deeper. Accessible websites embody the best values of the web—openness, inclusivity, and the idea that everyone deserves equal access to information.
You're now equipped to build the inclusive web of the future. Every accessible site you create makes the internet a more welcoming place for everyone.
You're now equipped to build the inclusive web of the future. Every accessible site you create makes the internet a more welcoming place for everyone. That's pretty amazing when you think about it!
## Additional Resources

@ -1,16 +1,50 @@
# AI Framework
There are many AI frameworks out there that when used can severely quicken up the time it takes to build a project. In this project we will focus on understanding what problems these frameworks address and build such a project ourselves.
Ever felt overwhelmed trying to build AI applications from scratch? You're not alone! AI frameworks are like having a Swiss Army knife for AI development - they're powerful tools that can save you time and headaches when building intelligent applications. Think of an AI framework as a well-organized library: it provides pre-built components, standardized APIs, and smart abstractions so you can focus on solving problems instead of wrestling with implementation details.
## Why a framework
In this lesson, we'll explore how frameworks like LangChain can turn what used to be complex AI integration tasks into clean, readable code. You'll discover how to tackle real-world challenges like keeping track of conversations, implementing tool calling, and juggling different AI models through one unified interface.
When it comes to using AI there are different approaches and different reasons for choosing these approaches, here are some:
By the time we're done, you'll know when to reach for frameworks instead of raw API calls, how to use their abstractions effectively, and how to build AI applications that are ready for real-world use. Let's explore what AI frameworks can do for your projects.
- **No SDK**, most AI models allows you to interact directly with the AI model via for example HTTP requests. That approach works and may sometimes be your only option if an SDK option is missing.
- **SDK**. Using an SDK is usually the recommended approach as it allows you to type less code to interact with your model. It usually is limited to a specific model and if using different models, you might need to write new code to support those additional models.
- **A framework**. A framework usually takes things to another level in the sense that if you need to use different models, there's one API for all of them, what differs is usually the initial set up. Additionally frameworks brings in useful abstractions like in the AI space, they can deal with tools, memory, workflows, agents and more while writing less code. Because frameworks are usually opinionated they can really be helpful if you buy into how they do things but may fall short if you try to do something bespoke that the framework isn't made for. Sometimes a framework can also simplify too much and you may therefore not learn an important topic that later may harm performance for example.
## Why choose a framework?
Generally, use the right tool for the job.
So you're ready to build an AI app - awesome! But here's the thing: you've got several different paths you can take, and each one has its own pros and cons. It's kind of like choosing between walking, biking, or driving to get somewhere - they'll all get you there, but the experience (and effort) will be totally different.
Let's break down the three main ways you can integrate AI into your projects:
| Approach | Advantages | Best For | Considerations |
|----------|------------|----------|--------------|
| **Direct HTTP Requests** | Full control, no dependencies | Simple queries, learning fundamentals | More verbose code, manual error handling |
| **SDK Integration** | Less boilerplate, model-specific optimization | Single-model applications | Limited to specific providers |
| **AI Frameworks** | Unified API, built-in abstractions | Multi-model apps, complex workflows | Learning curve, potential over-abstraction |
### Framework Benefits in Practice
```mermaid
graph TD
A[Your Application] --> B[AI Framework]
B --> C[OpenAI GPT]
B --> D[Anthropic Claude]
B --> E[GitHub Models]
B --> F[Local Models]
B --> G[Built-in Tools]
G --> H[Memory Management]
G --> I[Conversation History]
G --> J[Function Calling]
G --> K[Error Handling]
```
**Why frameworks matter:**
- **Unifies** multiple AI providers under one interface
- **Handles** conversation memory automatically
- **Provides** ready-made tools for common tasks like embeddings and function calling
- **Manages** error handling and retry logic
- **Turns** complex workflows into readable method calls
> 💡 **Pro Tip**: Use frameworks when switching between different AI models or building complex features like agents, memory, or tool calling. Stick with direct APIs when learning the basics or building simple, focused applications.
**Bottom line**: Like choosing between a craftsman's specialized tools and a complete workshop, it's about matching the tool to the task. Frameworks excel for complex, feature-rich applications, while direct APIs work well for straightforward use cases.
## Introduction
@ -20,13 +54,13 @@ In this lesson, we'll learn to:
- Address common problems like chat conversations, tool usage, memory and context.
- Leverage this to build AI apps.
## First prompt
## Your first AI prompt
In our first app example, we'll learn how to connect to an AI model and query it using a prompt.
Let's start with the fundamentals by creating your first AI application that sends a question and gets an answer back. Like Archimedes discovering the principle of displacement in his bath, sometimes the simplest observations lead to the most powerful insights - and frameworks make these insights accessible.
### Using Python
### Setting up LangChain with GitHub Models
For this example, we'll use Langchain to connect to GitHub Models. We can use a class called `ChatOpenAI` and give it the fields `api_key`, `base_url` and `model`. The token is something that automatically is populated within GitHub Codespaces and if you're running the app locally, you need to set up a personal access token for this to work.
We're going to use LangChain to connect to GitHub Models, which is pretty sweet because it gives you free access to various AI models. The best part? You only need a few simple configuration parameters to get started:
```python
from langchain_openai import ChatOpenAI
@ -38,32 +72,59 @@ llm = ChatOpenAI(
model="openai/gpt-4o-mini",
)
# works
response = llm.invoke("What's the capital of France?")
# Send a simple prompt
response = llm.invoke("What's the capital of France?")
print(response.content)
```
In this code, we:
**Let's break down what's happening here:**
- **Creates** a LangChain client using the `ChatOpenAI` class - this is your gateway to AI!
- **Configures** the connection to GitHub Models with your authentication token
- **Specifies** which AI model to use (`gpt-4o-mini`) - think of this as choosing your AI assistant
- **Sends** your question using the `invoke()` method - this is where the magic happens
- **Extracts** and displays the response - and voilà, you're chatting with AI!
- Call `ChatOpenAI` to create a client.
- Use `llm.invoke` with a prompt to create a response
- Print the response with `print(response.content)`.
You should see a response similar to:
> 🔧 **Setup Note**: If you're using GitHub Codespaces, you're in luck - the `GITHUB_TOKEN` is already set up for you! Working locally? No worries, you'll just need to create a personal access token with the right permissions.
**Expected output:**
```text
The capital of France is Paris.
```
## Chat conversation
```mermaid
sequenceDiagram
participant App as Your Python App
participant LC as LangChain
participant GM as GitHub Models
participant AI as GPT-4o-mini
App->>LC: llm.invoke("What's the capital of France?")
LC->>GM: HTTP request with prompt
GM->>AI: Process prompt
AI->>GM: Generated response
GM->>LC: Return response
LC->>App: response.content
```
In the preceding section, you saw how we used what's normally known as zero shot prompting, a single prompt followed by a response.
## Building conversational AI
However, often you find yourself in a situation where you need to maintain a conversation of several messages being exchanged between yourself and the AI assistant.
That first example demonstrates the basics, but it's just a single exchange - you ask a question, get an answer, and that's it. In real applications, you want your AI to remember what you've been discussing, like how Watson and Holmes built their investigative conversations over time.
### Using Python
This is where LangChain becomes particularly useful. It provides different message types that help structure conversations and let you give your AI a personality. You'll be building chat experiences that maintain context and character.
In langchain, we can store the conversation in a list. The `HumanMessage` represents a message from a user, and `SystemMessage` is a message meant to set the "personality" of the AI. In below example you see how we instruct the AI to assume the personality of Captain Picard and for the human/user to ask "Tell me about you" as the prompt.
### Understanding message types
Think of these message types as different "hats" that participants wear in a conversation. LangChain uses different message classes to keep track of who's saying what:
| Message Type | Purpose | Example Use Case |
|--------------|---------|------------------|
| `SystemMessage` | Defines AI personality and behavior | "You are a helpful coding assistant" |
| `HumanMessage` | Represents user input | "Explain how functions work" |
| `AIMessage` | Stores AI responses | Previous AI responses in conversation |
### Creating your first conversation
Let's create a conversation where our AI assumes a specific role. We'll have it embody Captain Picard - a character known for his diplomatic wisdom and leadership:
```python
messages = [
@ -72,6 +133,11 @@ messages = [
]
```
**Breaking down this conversation setup:**
- **Establishes** the AI's role and personality through `SystemMessage`
- **Provides** the initial user query via `HumanMessage`
- **Creates** a foundation for multi-turn conversation
The full code for this example looks like so:
```python
@ -106,7 +172,7 @@ I believe in the importance of diplomacy, reason, and the pursuit of knowledge.
I hold the ideals of the Federation close to my heart, believing in the importance of cooperation, understanding, and respect for all sentient beings. My experiences have shaped my leadership style, and I strive to be a thoughtful and just captain. How may I assist you further?
```
To keep the state of the conversation, you can add the response from a chat, so conversation is remembered, here's how to do that:
To maintain conversation continuity (instead of resetting context each time), you need to keep adding responses to your message list. Like the oral traditions that preserved stories across generations, this approach builds lasting memory:
```python
from langchain_core.messages import HumanMessage, SystemMessage
@ -141,9 +207,9 @@ print(response.content)
```
What we can see from the above conversation is how we invoke the LLM two times, first with the conversation consisting of just two messages but then a second time with more messages added to the conversation.
Pretty neat, right? What's happening here is that we're calling the LLM twice - first with just our initial two messages, but then again with the full conversation history. It's like the AI is actually following along with our chat!
In fact, if you run this, you will see the second response being something like:
When you run this code, you'll get a second response that sounds something like:
```text
Welcome aboard, Chris! It's always a pleasure to meet those who share a passion for exploration and discovery. While I cannot formally offer you a position on the Enterprise right now, I encourage you to pursue your aspirations. We are always in need of talented individuals with diverse skills and backgrounds.
@ -155,19 +221,121 @@ I'll take that as a maybe ;)
## Streaming responses
TODO
Ever notice how ChatGPT seems to "type" its responses in real-time? That's streaming in action. Like watching a skilled calligrapher work - seeing the characters appear stroke by stroke rather than materializing instantly - streaming makes the interaction feel more natural and provides immediate feedback.
### Implementing streaming with LangChain
```python
from langchain_openai import ChatOpenAI
import os
llm = ChatOpenAI(
api_key=os.environ["GITHUB_TOKEN"],
base_url="https://models.github.ai/inference",
model="openai/gpt-4o-mini",
streaming=True
)
# Stream the response
for chunk in llm.stream("Write a short story about a robot learning to code"):
print(chunk.content, end="", flush=True)
```
**Why streaming is awesome:**
- **Shows** content as it's being created - no more awkward waiting!
- **Makes** users feel like something's actually happening
- **Feels** faster, even when it technically isn't
- **Lets** users start reading while the AI is still "thinking"
> 💡 **User Experience Tip**: Streaming really shines when you're dealing with longer responses like code explanations, creative writing, or detailed tutorials. Your users will love seeing progress instead of staring at a blank screen!
## Prompt templates
TODO
Prompt templates work like the rhetorical structures used in classical oratory - think of how Cicero would adapt his speech patterns for different audiences while maintaining the same persuasive framework. They let you create reusable prompts where you can swap out different pieces of information without rewriting everything from scratch. Once you set up the template, you just fill in the variables with whatever values you need.
### Creating reusable prompts
```python
from langchain_core.prompts import ChatPromptTemplate
# Define a template for code explanations
template = ChatPromptTemplate.from_messages([
("system", "You are an expert programming instructor. Explain concepts clearly with examples."),
("human", "Explain {concept} in {language} with a practical example for {skill_level} developers")
])
# Use the template with different values
questions = [
{"concept": "functions", "language": "JavaScript", "skill_level": "beginner"},
{"concept": "classes", "language": "Python", "skill_level": "intermediate"},
{"concept": "async/await", "language": "JavaScript", "skill_level": "advanced"}
]
for question in questions:
prompt = template.format_messages(**question)
response = llm.invoke(prompt)
print(f"Topic: {question['concept']}\n{response.content}\n---\n")
```
**Why you'll love using templates:**
- **Keeps** your prompts consistent across your entire app
- **No more** messy string concatenation - just clean, simple variables
- **Your AI** behaves predictably because the structure stays the same
- **Updates** are a breeze - change the template once, and it's fixed everywhere
## Structured output
TODO
Ever get frustrated trying to parse AI responses that come back as unstructured text? Structured output is like teaching your AI to follow the systematic approach that Linnaeus used for biological classification - organized, predictable, and easy to work with. You can request JSON, specific data structures, or any format you need.
### Defining output schemas
```python
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import JsonOutputParser
from pydantic import BaseModel, Field
class CodeReview(BaseModel):
score: int = Field(description="Code quality score from 1-10")
strengths: list[str] = Field(description="List of code strengths")
improvements: list[str] = Field(description="List of suggested improvements")
overall_feedback: str = Field(description="Summary feedback")
# Set up the parser
parser = JsonOutputParser(pydantic_object=CodeReview)
# Create prompt with format instructions
prompt = ChatPromptTemplate.from_messages([
("system", "You are a code reviewer. {format_instructions}"),
("human", "Review this code: {code}")
])
# Format the prompt with instructions
chain = prompt | llm | parser
# Get structured response
code_sample = """
def calculate_average(numbers):
return sum(numbers) / len(numbers)
"""
result = chain.invoke({
"code": code_sample,
"format_instructions": parser.get_format_instructions()
})
print(f"Score: {result['score']}")
print(f"Strengths: {', '.join(result['strengths'])}")
```
**Why structured output is a game-changer:**
- **No more** guessing what format you'll get back - it's consistent every time
- **Plugs** directly into your databases and APIs without extra work
- **Catches** weird AI responses before they break your app
- **Makes** your code cleaner because you know exactly what you're working with
## Tool calling
Tools are how we give the LLM extra skills. The idea is to tell the LLM about functions it has and if a prompt is made that matches the description of one of these tools then we call them.
Now we reach one of the most powerful features: tools. This is how you give your AI practical capabilities beyond conversation. Like how medieval guilds developed specialized tools for specific crafts, you can equip your AI with focused instruments. You describe what tools are available, and when someone requests something that matches, your AI can take action.
### Using Python
@ -190,7 +358,7 @@ functions = {
}
```
What we're doing here is to create a description of a tool called `add`. By inheriting from `TypedDict` and adding members like `a` and `b` of type `Annotated` this can be converted to a schema that the LLM can understand. The creation of functions is a dictionary that ensures that we know what to do if a specific tool is identified.
So what's happening here? We're creating a blueprint for a tool called `add`. By inheriting from `TypedDict` and using those fancy `Annotated` types for `a` and `b`, we're giving the LLM a clear picture of what this tool does and what it needs. The `functions` dictionary is like our toolbox - it tells our code exactly what to do when the AI decides to use a specific tool.
Let's see how we call the LLM with this tool next:
@ -263,15 +431,15 @@ TOOL CALL: 15
CONTENT:
```
What this output mean is that the LLM analyzed the prompt "What is 3 + 12" as meaning that the `add` tool should be called and it knew that thanks to its name, description and member field descriptions. That the answer is 15 is because of our code using the dictionary `functions` to invoke it:
The AI examined "What is 3 + 12" and recognized this as a task for the `add` tool. Like how a skilled librarian knows which reference to consult based on the type of question asked, it made this determination from the tool's name, description, and field specifications. The result of 15 comes from our `functions` dictionary executing the tool:
```python
print("TOOL CALL: ", functions[tool["name"]](**tool["args"]))
```
### A more interesting tool that calls a web api
### A more interesting tool that calls a web API
Tools that adds two numbers is interesting as it illustrates how tool calling works but usually tools tend to do something more interesting like for example calling a Web API, let's do just that with this code:
Adding numbers demonstrates the concept, but real tools typically perform more complex operations, like calling web APIs. Let's expand our example to have the AI fetch content from the internet - similar to how telegraph operators once connected distant locations:
```python
class joke(TypedDict):
@ -355,20 +523,342 @@ if(res.tool_calls):
print("CONTENT: ",res.content)
```
## Embedding
## Embeddings and document processing
Embeddings represent one of the most elegant solutions in modern AI. Imagine if you could take any piece of text and convert it into numerical coordinates that capture its meaning. That's exactly what embeddings do - they transform text into points in multi-dimensional space where similar concepts cluster together. It's like having a coordinate system for ideas, reminiscent of how Mendeleev organized the periodic table by atomic properties.
### Creating and using embeddings
```python
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import FAISS
from langchain_community.document_loaders import TextLoader
from langchain.text_splitter import CharacterTextSplitter
# Initialize embeddings
embeddings = OpenAIEmbeddings(
api_key=os.environ["GITHUB_TOKEN"],
base_url="https://models.github.ai/inference",
model="text-embedding-3-small"
)
# Load and split documents
loader = TextLoader("documentation.txt")
documents = loader.load()
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
texts = text_splitter.split_documents(documents)
# Create vector store
vectorstore = FAISS.from_documents(texts, embeddings)
# Perform similarity search
query = "How do I handle user authentication?"
similar_docs = vectorstore.similarity_search(query, k=3)
for doc in similar_docs:
print(f"Relevant content: {doc.page_content[:200]}...")
```
### Document loaders for various formats
```python
from langchain_community.document_loaders import (
PyPDFLoader,
CSVLoader,
JSONLoader,
WebBaseLoader
)
# Load different document types
pdf_loader = PyPDFLoader("manual.pdf")
csv_loader = CSVLoader("data.csv")
json_loader = JSONLoader("config.json")
web_loader = WebBaseLoader("https://example.com/docs")
# Process all documents
all_documents = []
for loader in [pdf_loader, csv_loader, json_loader, web_loader]:
docs = loader.load()
all_documents.extend(docs)
```
**What you can do with embeddings:**
- **Build** search that actually understands what you mean, not just keyword matching
- **Create** AI that can answer questions about your documents
- **Make** recommendation systems that suggest truly relevant content
- **Automatically** organize and categorize your content
## Building a complete AI application
Now we'll integrate everything you've learned into a comprehensive application - a coding assistant that can answer questions, use tools, and maintain conversation memory. Like how the printing press combined existing technologies (movable type, ink, paper, and pressure) into something transformative, we'll combine our AI components into something practical and useful.
vectorize content, compare via cosine similarity
### Complete application example
```python
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage
from langchain_community.vectorstores import FAISS
from typing_extensions import Annotated, TypedDict
import os
import requests
class CodingAssistant:
def __init__(self):
self.llm = ChatOpenAI(
api_key=os.environ["GITHUB_TOKEN"],
base_url="https://models.github.ai/inference",
model="openai/gpt-4o-mini"
)
self.conversation_history = [
SystemMessage(content="""You are an expert coding assistant.
Help users learn programming concepts, debug code, and write better software.
Use tools when needed and maintain a helpful, encouraging tone.""")
]
# Define tools
self.setup_tools()
def setup_tools(self):
class web_search(TypedDict):
"""Search for programming documentation or examples."""
query: Annotated[str, "Search query for programming help"]
class code_formatter(TypedDict):
"""Format and validate code snippets."""
code: Annotated[str, "Code to format"]
language: Annotated[str, "Programming language"]
self.tools = [web_search, code_formatter]
self.llm_with_tools = self.llm.bind_tools(self.tools)
def chat(self, user_input: str):
# Add user message to conversation
self.conversation_history.append(HumanMessage(content=user_input))
# Get AI response
response = self.llm_with_tools.invoke(self.conversation_history)
# Handle tool calls if any
if response.tool_calls:
for tool_call in response.tool_calls:
tool_result = self.execute_tool(tool_call)
print(f"🔧 Tool used: {tool_call['name']}")
print(f"📊 Result: {tool_result}")
# Add AI response to conversation
self.conversation_history.append(response)
return response.content
def execute_tool(self, tool_call):
tool_name = tool_call['name']
args = tool_call['args']
if tool_name == 'web_search':
return f"Found documentation for: {args['query']}"
elif tool_name == 'code_formatter':
return f"Formatted {args['language']} code: {args['code'][:50]}..."
return "Tool execution completed"
# Usage example
assistant = CodingAssistant()
print("🤖 Coding Assistant Ready! Type 'quit' to exit.\n")
while True:
user_input = input("You: ")
if user_input.lower() == 'quit':
break
response = assistant.chat(user_input)
print(f"🤖 Assistant: {response}\n")
```
**Application architecture:**
```mermaid
graph TD
A[User Input] --> B[Coding Assistant]
B --> C[Conversation Memory]
B --> D[Tool Detection]
B --> E[LLM Processing]
D --> F[Web Search Tool]
D --> G[Code Formatter Tool]
E --> H[Response Generation]
F --> H
G --> H
H --> I[User Interface]
H --> C
```
https://python.langchain.com/docs/how_to/embed_text/
**Key features we've implemented:**
- **Remembers** your entire conversation for context continuity
- **Performs actions** through tool calling, not just conversation
- **Follows** predictable interaction patterns
- **Manages** error handling and complex workflows automatically
### document loaders
## Assignment: Build your own AI-powered study assistant
pdf and csv
**Objective**: Create an AI application that helps students learn programming concepts by providing explanations, code examples, and interactive quizzes.
## Building an app
### Requirements
TODO
**Core Features (Required):**
1. **Conversational Interface**: Implement a chat system that maintains context across multiple questions
2. **Educational Tools**: Create at least two tools that help with learning:
- Code explanation tool
- Concept quiz generator
3. **Personalized Learning**: Use system messages to adapt responses to different skill levels
4. **Response Formatting**: Implement structured output for quiz questions
## Assignment
### Implementation Steps
**Step 1: Setup your environment**
```bash
pip install langchain langchain-openai
```
**Step 2: Basic chat functionality**
- Create a `StudyAssistant` class
- Implement conversation memory
- Add personality configuration for educational support
**Step 3: Add educational tools**
- **Code Explainer**: Breaks down code into understandable parts
- **Quiz Generator**: Creates questions about programming concepts
- **Progress Tracker**: Keeps track of topics covered
**Step 4: Enhanced features (Optional)**
- Implement streaming responses for better user experience
- Add document loading to incorporate course materials
- Create embeddings for similarity-based content retrieval
### Evaluation Criteria
| Feature | Excellent (4) | Good (3) | Satisfactory (2) | Needs Work (1) |
|---------|---------------|----------|------------------|----------------|
| **Conversation Flow** | Natural, context-aware responses | Good context retention | Basic conversation | No memory between exchanges |
| **Tool Integration** | Multiple useful tools working seamlessly | 2+ tools implemented correctly | 1-2 basic tools | Tools not functional |
| **Code Quality** | Clean, well-documented, error handling | Good structure, some documentation | Basic functionality works | Poor structure, no error handling |
| **Educational Value** | Truly helpful for learning, adaptive | Good learning support | Basic explanations | Limited educational benefit |
### Sample code structure
```python
class StudyAssistant:
def __init__(self, skill_level="beginner"):
# Initialize LLM, tools, and conversation memory
pass
def explain_code(self, code, language):
# Tool: Explain how code works
pass
def generate_quiz(self, topic, difficulty):
# Tool: Create practice questions
pass
def chat(self, user_input):
# Main conversation interface
pass
# Example usage
assistant = StudyAssistant(skill_level="intermediate")
response = assistant.chat("Explain how Python functions work")
```
**Bonus Challenges:**
- Add voice input/output capabilities
- Implement a web interface using Streamlit or Flask
- Create a knowledge base from course materials using embeddings
- Add progress tracking and personalized learning paths
## Summary
🎉 You've now mastered the fundamentals of AI framework development and learned how to build sophisticated AI applications using LangChain. Like completing a comprehensive apprenticeship, you've acquired a substantial toolkit of skills. Let's review what you've accomplished.
### What you've learned
**Core Framework Concepts:**
- **Framework Benefits**: Understanding when to choose frameworks over direct API calls
- **LangChain Basics**: Setting up and configuring AI model connections
- **Message Types**: Using `SystemMessage`, `HumanMessage`, and `AIMessage` for structured conversations
**Advanced Features:**
- **Tool Calling**: Creating and integrating custom tools for enhanced AI capabilities
- **Conversation Memory**: Maintaining context across multiple conversation turns
- **Streaming Responses**: Implementing real-time response delivery
- **Prompt Templates**: Building reusable, dynamic prompts
- **Structured Output**: Ensuring consistent, parseable AI responses
- **Embeddings**: Creating semantic search and document processing capabilities
**Practical Applications:**
- **Building Complete Apps**: Combining multiple features into production-ready applications
- **Error Handling**: Implementing robust error management and validation
- **Tool Integration**: Creating custom tools that extend AI capabilities
### Key takeaways
> 🎯 **Remember**: AI frameworks like LangChain are basically your complexity-hiding, feature-packed best friends. They're perfect when you need conversation memory, tool calling, or want to work with multiple AI models without losing your sanity.
**Decision framework for AI integration:**
```mermaid
flowchart TD
A[AI Integration Need] --> B{Simple single query?}
B -->|Yes| C[Direct API calls]
B -->|No| D{Need conversation memory?}
D -->|No| E[SDK Integration]
D -->|Yes| F{Need tools or complex features?}
F -->|No| G[Framework with basic setup]
F -->|Yes| H[Full framework implementation]
C --> I[HTTP requests, minimal dependencies]
E --> J[Provider SDK, model-specific]
G --> K[LangChain basic chat]
H --> L[LangChain with tools, memory, agents]
```
### Where do you go from here?
**Start building right now:**
- Take these concepts and build something that excites YOU!
- Play around with different AI models through LangChain - it's like having a playground of AI models
- Create tools that solve actual problems you face in your work or projects
**Ready for the next level?**
- **AI Agents**: Build AI systems that can actually plan and execute complex tasks on their own
- **RAG (Retrieval-Augmented Generation)**: Combine AI with your own knowledge bases for super-powered applications
- **Multi-Modal AI**: Work with text, images, and audio all together - the possibilities are endless!
- **Production Deployment**: Learn how to scale your AI apps and monitor them in the real world
**Join the community:**
- The LangChain community is fantastic for staying up-to-date and learning best practices
- GitHub Models gives you access to cutting-edge AI capabilities - perfect for experimenting
- Keep practicing with different use cases - each project will teach you something new
You now have the knowledge to build intelligent, conversational applications that can help people solve real problems. Like the Renaissance craftsmen who combined artistic vision with technical skill, you can now merge AI capabilities with practical application. The question is: what will you create? 🚀
## GitHub Copilot Agent Challenge 🚀
Use the Agent mode to complete the following challenge:
**Description:** Build an advanced AI-powered code review assistant that combines multiple LangChain features including tool calling, structured output, and conversation memory to provide comprehensive feedback on code submissions.
**Prompt:** Create a CodeReviewAssistant class that implements:
1. A tool for analyzing code complexity and suggesting improvements
2. A tool for checking code against best practices
3. Structured output using Pydantic models for consistent review format
4. Conversation memory to track review sessions
5. A main chat interface that can handle code submissions and provide detailed, actionable feedback
The assistant should be able to review code in multiple programming languages, maintain context across multiple code submissions in a session, and provide both summary scores and detailed improvement suggestions.
Learn more about [agent mode](https://code.visualstudio.com/blogs/2025/02/24/introducing-copilot-agent-mode) here.

@ -3,6 +3,12 @@
![JavaScript Basics - Data types](../../sketchnotes/webdev101-js-datatypes.png)
> Sketchnote by [Tomomi Imura](https://twitter.com/girlie_mac)
Data types are one of the fundamental concepts in JavaScript that you'll encounter in every program you write. Think of data types like the filing system used by ancient librarians in Alexandria they had specific places for scrolls containing poetry, mathematics, and historical records. JavaScript organizes information in a similar way with different categories for different kinds of data.
In this lesson, we'll explore the core data types that make JavaScript work. You'll learn how to handle numbers, text, true/false values, and understand why choosing the correct type is essential for your programs. These concepts might seem abstract at first, but with practice, they'll become second nature.
Understanding data types will make everything else in JavaScript much clearer. Just as architects need to understand different building materials before constructing a cathedral, these fundamentals will support everything you build going forward.
## Pre-Lecture Quiz
[Pre-lecture quiz](https://ff-quizzes.netlify.app/web/)
@ -17,108 +23,163 @@ This lesson covers the basics of JavaScript, the language that provides interact
> 🎥 Click the images above for videos about variables and data types
Let's start with variables and the data types that populate them!
## Variables
Variables store values that can be used and changed throughout your code.
Variables are fundamental building blocks in programming. Like the labeled jars that medieval alchemists used to store different substances, variables let you store information and give it a descriptive name so you can reference it later. Need to remember someone's age? Store it in a variable called `age`. Want to track a user's name? Keep it in a variable called `userName`.
We'll focus on the modern approach to creating variables in JavaScript. The techniques you'll learn here represent years of language evolution and best practices developed by the programming community.
Creating and **declaring** a variable has the following syntax **[keyword] [name]**. It's made up of the two parts:
- **Keyword**. Keywords can be `let` or `var`.
- **Keyword**. Use `let` for variables that can change, or `const` for values that stay the same.
- **The variable name**, this is a descriptive name you choose yourself.
✅ The keyword `let` was introduced in ES6 and gives your variable a so called _block scope_. It's recommended that you use `let` over `var`. We will cover block scopes more in depth in future parts.
- **The variable name**, this is a name you choose yourself.
✅ The keyword `let` was introduced in ES6 and gives your variable a so called _block scope_. It's recommended that you use `let` or `const` instead of the older `var` keyword. We will cover block scopes more in depth in future parts.
### Task - working with variables
1. **Declare a variable**. Let's declare a variable using the `let` keyword:
1. **Declare a variable**. Let's start by creating our first variable:
```javascript
let myVariable;
```
`myVariable` has now been declared using the `let` keyword. It currently doesn't have a value.
**What this accomplishes:**
- This tells JavaScript to create a storage location called `myVariable`
- JavaScript allocates space in memory for this variable
- The variable currently has no value (undefined)
1. **Assign a value**. Store a value in a variable with the `=` operator, followed by the expected value.
2. **Give it a value**. Now let's put something in our variable:
```javascript
myVariable = 123;
```
> Note: the use of `=` in this lesson means we make use of an "assignment operator", used to set a value to a variable. It doesn't denote equality.
**How assignment works:**
- The `=` operator assigns the value 123 to our variable
- The variable now contains this value instead of being undefined
- You can reference this value throughout your code using `myVariable`
`myVariable` has now been *initialized* with the value 123.
> Note: the use of `=` in this lesson means we make use of an "assignment operator", used to set a value to a variable. It doesn't denote equality.
1. **Refactor**. Replace your code with the following statement.
3. **Do it the smart way**. Actually, let's combine those two steps:
```javascript
let myVariable = 123;
```
The above is called an _explicit initialization_ when a variable is declared and is assigned a value at the same time.
**This approach is more efficient:**
- You're declaring the variable and assigning a value in one statement
- This is the standard practice among developers
- It reduces code length while maintaining clarity
1. **Change the variable value**. Change the variable value in the following way:
4. **Change your mind**. What if we want to store a different number?
```javascript
myVariable = 321;
```
Once a variable is declared, you can change its value at any point in your code with the `=` operator and the new value.
**Understanding reassignment:**
- The variable now contains 321 instead of 123
- The previous value is replaced variables store only one value at a time
- This mutability is the key characteristic of variables declared with `let`
✅ Try it! You can write JavaScript right in your browser. Open a browser window and navigate to Developer Tools. In the console, you will find a prompt; type `let myVariable = 123`, press return, then type `myVariable`. What happens? Note, you'll learn more about these concepts in subsequent lessons.
## Constants
Sometimes you need to store information that should never change during program execution. Think of constants like the mathematical principles that Euclid established in ancient Greece once proven and documented, they remained fixed for all future reference.
Constants work similarly to variables, but with an important restriction: once you assign their value, it cannot be changed. This immutability helps prevent accidental modifications to critical values in your program.
Declaration and initialization of a constant follows the same concepts as a variable, with the exception of the `const` keyword. Constants are typically declared with all uppercase letters.
```javascript
const MY_VARIABLE = 123;
```
Constants are similar to variables, with two exceptions:
**Here's what this code does:**
- **Creates** a constant named `MY_VARIABLE` with the value 123
- **Uses** uppercase naming convention for constants
- **Prevents** any future changes to this value
Constants have two main rules:
- **You must give them a value right away** no empty constants allowed!
- **You can never change that value** JavaScript will throw an error if you try. Let's see what I mean:
- **Must have a value**. Constants must be initialized, or an error will occur when running code.
- **Reference cannot be changed**. The reference of a constant cannot be changed once initialized, or an error will occur when running code. Let's look at two examples:
- **Simple value**. The following is NOT allowed:
**Simple value** - The following is NOT allowed:
```javascript
const PI = 3;
PI = 4; // not allowed
```
**What you need to remember:**
- **Attempts** to reassign a constant will cause an error
- **Protects** important values from accidental changes
- **Ensures** the value remains consistent throughout your program
- **Object reference is protected**. The following is NOT allowed.
**Object reference is protected** - The following is NOT allowed:
```javascript
const obj = { a: 3 };
obj = { b: 5 } // not allowed
```
- **Object value is not protected**. The following IS allowed:
**Understanding these concepts:**
- **Prevents** replacing the entire object with a new one
- **Protects** the reference to the original object
- **Maintains** the object's identity in memory
**Object value is not protected** - The following IS allowed:
```javascript
const obj = { a: 3 };
obj.a = 5; // allowed
```
Above you are changing the value of the object but not the reference itself, which makes it allowed.
**Breaking down what happens here:**
- **Modifies** the property value inside the object
- **Keeps** the same object reference
- **Demonstrates** that object contents can change while the reference stays constant
> Note, a `const` means the reference is protected from reassignment. The value is not _immutable_ though and can change, especially if it's a complex construct like an object.
## Data Types
JavaScript organizes information into different categories called data types. This concept mirrors how ancient scholars categorized knowledge Aristotle distinguished between different types of reasoning, knowing that logical principles couldn't be applied uniformly to poetry, mathematics, and natural philosophy.
Data types matter because different operations work with different kinds of information. Just as you can't perform arithmetic on a person's name or alphabetize a mathematical equation, JavaScript requires the appropriate data type for each operation. Understanding this prevents errors and makes your code more reliable.
Variables can store many different types of values, like numbers and text. These various types of values are known as the **data type**. Data types are an important part of software development because it helps developers make decisions on how the code should be written and how the software should run. Furthermore, some data types have unique features that help transform or extract additional information in a value.
✅ Data Types are also referred to as JavaScript data primitives, as they are the lowest-level data types that are provided by the language. There are 7 primitive data types: string, number, bigint, boolean, undefined, null and symbol. Take a minute to visualize what each of these primitives might represent. What is a `zebra`? How about `0`? `true`?
### Numbers
In the previous section, the value of `myVariable` was a number data type.
Numbers are the most straightforward data type in JavaScript. Whether you're working with whole numbers like 42, decimals like 3.14, or negative numbers like -5, JavaScript handles them uniformly.
Remember our variable from earlier? That 123 we stored was actually a number data type:
`let myVariable = 123;`
```javascript
let myVariable = 123;
```
**Key characteristics:**
- JavaScript automatically recognizes numeric values
- You can perform mathematical operations with these variables
- No explicit type declaration is required
Variables can store all types of numbers, including decimals or negative numbers. Numbers also can be used with arithmetic operators, covered in the [next section](#arithmetic-operators).
### Arithmetic Operators
Arithmetic operators allow you to perform mathematical calculations in JavaScript. These operators follow the same principles mathematicians have used for centuries the same symbols that appeared in the works of scholars like Al-Khwarizmi, who developed algebraic notation.
The operators work as you would expect from traditional mathematics: plus for addition, minus for subtraction, and so forth.
There are several types of operators to use when performing arithmetic functions, and some are listed here:
| Symbol | Description | Example |
@ -133,17 +194,31 @@ There are several types of operators to use when performing arithmetic functions
### Strings
In JavaScript, textual data is represented as strings. The term "string" comes from the concept of characters strung together in sequence, much like the way scribes in medieval monasteries would connect letters to form words and sentences in their manuscripts.
Strings are fundamental to web development. Every piece of text displayed on a website usernames, button labels, error messages, content is handled as string data. Understanding strings is essential for creating functional user interfaces.
Strings are sets of characters that reside between single or double quotes.
- `'This is a string'`
- `"This is also a string"`
- `let myString = 'This is a string value stored in a variable';`
```javascript
'This is a string'
"This is also a string"
let myString = 'This is a string value stored in a variable';
```
**Understanding these concepts:**
- **Uses** either single quotes `'` or double quotes `"` to define strings
- **Stores** text data that can include letters, numbers, and symbols
- **Assigns** string values to variables for later use
- **Requires** quotes to distinguish text from variable names
Remember to use quotes when writing a string, or else JavaScript will assume it's a variable name.
### Formatting Strings
Strings are textual, and will require formatting from time to time.
String manipulation allows you to combine text elements, incorporate variables, and create dynamic content that responds to program state. This technique enables you to construct text programmatically.
Often you need to join multiple strings together this process is called concatenation.
To **concatenate** two or more strings, or join them together, use the `+` operator.
@ -154,9 +229,14 @@ let myString2 = "World";
myString1 + myString2 + "!"; //HelloWorld!
myString1 + " " + myString2 + "!"; //Hello World!
myString1 + ", " + myString2 + "!"; //Hello, World!
```
**Step by step, here's what's happening:**
- **Combines** multiple strings using the `+` operator
- **Joins** strings directly together without spaces in the first example
- **Adds** space characters `" "` between strings for readability
- **Inserts** punctuation like commas to create proper formatting
✅ Why does `1 + 1 = 2` in JavaScript, but `'1' + '1' = 11?` Think about it. What about `'1' + 1`?
**Template literals** are another way to format strings, except instead of quotes, the backtick is used. Anything that is not plain text must be placed inside placeholders `${ }`. This includes any variables that may be strings.
@ -169,24 +249,54 @@ let myString2 = "World";
`${myString1}, ${myString2}!` //Hello, World!
```
**Let's understand each part:**
- **Uses** backticks `` ` `` instead of regular quotes to create template literals
- **Embeds** variables directly using `${}` placeholder syntax
- **Preserves** spaces and formatting exactly as written
- **Provides** a cleaner way to create complex strings with variables
You can achieve your formatting goals with either method, but template literals will respect any spaces and line breaks.
✅ When would you use a template literal vs. a plain string?
### Booleans
Booleans represent the simplest form of data: they can only hold one of two values `true` or `false`. This binary logic system traces back to the work of George Boole, a 19th-century mathematician who developed Boolean algebra.
Despite their simplicity, booleans are essential for program logic. They enable your code to make decisions based on conditions whether a user is logged in, if a button was clicked, or if certain criteria are met.
Booleans can be only two values: `true` or `false`. Booleans can help make decisions on which lines of code should run when certain conditions are met. In many cases, [operators](#arithmetic-operators) assist with setting the value of a Boolean and you will often notice and write variables being initialized or their values being updated with an operator.
- `let myTrueBool = true`
- `let myFalseBool = false`
```javascript
let myTrueBool = true;
let myFalseBool = false;
```
**In the above, we've:**
- **Created** a variable that stores the Boolean value `true`
- **Demonstrated** how to store the Boolean value `false`
- **Used** the exact keywords `true` and `false` (no quotes needed)
- **Prepared** these variables for use in conditional statements
✅ A variable can be considered 'truthy' if it evaluates to a boolean `true`. Interestingly, in JavaScript, [all values are truthy unless defined as falsy](https://developer.mozilla.org/docs/Glossary/Truthy).
---
## GitHub Copilot Agent Challenge 🚀
Use the Agent mode to complete the following challenge:
**Description:** Create a personal information manager that demonstrates all the JavaScript data types you've learned in this lesson while handling real-world data scenarios.
**Prompt:** Build a JavaScript program that creates a user profile object containing: a person's name (string), age (number), is a student status (boolean), favorite colors as an array, and an address object with street, city, and zip code properties. Include functions to display the profile information and update individual fields. Make sure to demonstrate string concatenation, template literals, arithmetic operations with the age, and boolean logic for the student status.
Learn more about [agent mode](https://code.visualstudio.com/blogs/2025/02/24/introducing-copilot-agent-mode) here.
## 🚀 Challenge
JavaScript is notorious for its surprising ways of handling datatypes on occasion. Do a bit of research on these 'gotchas'. For example: case sensitivity can bite! Try this in your console: `let age = 1; let Age = 2; age == Age` (resolves `false` -- why?). What other gotchas can you find?
JavaScript has some behaviors that can catch developers off guard. Here's a classic example to explore: try typing this in your browser console: `let age = 1; let Age = 2; age == Age` and observe the result. It returns `false` can you determine why?
This represents one of many JavaScript behaviors worth understanding. Familiarity with these quirks will help you write more reliable code and debug issues more effectively.
## Post-Lecture Quiz
[Post-lecture quiz](https://ff-quizzes.netlify.app)

@ -1,11 +1,87 @@
# Data Types Practice
# Data Types Practice: E-commerce Shopping Cart
## Instructions
Imagine you are building a shopping cart. Write documentation on the data types you would need to complete your shopping experience. For each data type, explain how and why you would use it, and provide an example. The six JavaScript data types are: String, Number, Boolean, Null, Undefined, and Object.
Imagine you are building a modern e-commerce shopping cart system. This assignment will help you understand how different JavaScript data types work together to create real-world applications.
### Your Task
Create a comprehensive analysis of how you would use JavaScript data types in a shopping cart application. For each of the seven primitive data types and objects, you need to:
1. **Identify** the data type and its purpose
2. **Explain** why this data type is the best choice for specific shopping cart features
3. **Provide** realistic code examples showing the data type in action
4. **Describe** how this data type interacts with other parts of the shopping cart
### Required Data Types to Cover
**Primitive Data Types:**
- **String**: Product names, descriptions, user information
- **Number**: Prices, quantities, tax calculations
- **Boolean**: Item availability, user preferences, cart status
- **Null**: Intentionally empty values (like missing discount codes)
- **Undefined**: Uninitialized values or missing data
- **Symbol**: Unique identifiers (advanced use)
- **BigInt**: Large financial calculations (advanced use)
**Reference Types:**
- **Object**: Product details, user profiles, cart contents
- **Array**: List of products, order history, categories
### Example Format for Each Data Type
For each data type, structure your response like this:
```markdown
## [Data Type Name]
**Purpose in Shopping Cart:** [Explain what this data type does]
**Why This Type:** [Explain why this is the best choice]
**Code Example:**
```javascript
// Your realistic code example here
```
**Real-world Usage:** [Describe how this would work in practice]
**Interactions:** [Explain how this data type works with others]
```
### Bonus Challenges
1. **Type Coercion**: Show an example where JavaScript automatically converts between data types in your shopping cart (e.g., string "5" + number 10)
2. **Data Validation**: Demonstrate how you would check if user input is the correct data type before processing
3. **Performance Considerations**: Explain when you might choose one data type over another for performance reasons
### Submission Guidelines
- Create a markdown document with clear headings for each data type
- Include working JavaScript code examples
- Use realistic e-commerce scenarios in your examples
- Explain your reasoning clearly for beginners to understand
- Test your code examples to ensure they work correctly
## Rubric
Criteria | Exemplary | Adequate | Needs Improvement
--- | --- | --- | -- |
Data Types | All six data types are listed, explored in detail, and documented with examples | Four data types are explored with some explanation | Two data types are explored with minimal explanation |
| Criteria | Exemplary (90-100%) | Proficient (80-89%) | Developing (70-79%) | Needs Improvement (Below 70%) |
|----------|---------------------|---------------------|---------------------|------------------------------|
| **Data Type Coverage** | All 7 primitive types and objects/arrays covered with detailed explanations | 6-7 data types covered with good explanations | 4-5 data types covered with basic explanations | Fewer than 4 data types or minimal explanations |
| **Code Examples** | All examples are realistic, working, and well-commented | Most examples work and are relevant to e-commerce | Some examples work but may be generic | Code examples are incomplete or non-functional |
| **Real-world Application** | Clearly connects each data type to practical shopping cart features | Good connection to e-commerce scenarios | Some connection to shopping cart context | Limited real-world application demonstrated |
| **Technical Accuracy** | All technical information is correct and demonstrates deep understanding | Most technical information is accurate | Generally accurate with minor errors | Contains significant technical errors |
| **Communication** | Explanations are clear, beginner-friendly, and well-organized | Good explanations that are mostly clear | Explanations are understandable but may lack clarity | Explanations are unclear or poorly organized |
| **Bonus Elements** | Includes multiple bonus challenges with excellent execution | Includes one or more bonus challenges well done | Attempts bonus challenges with mixed success | No bonus challenges attempted |
### Learning Objectives
By completing this assignment, you will:
- ✅ **Understand** the seven JavaScript primitive data types and their uses
- ✅ **Apply** data types to real-world programming scenarios
- ✅ **Analyze** when to choose specific data types for different purposes
- ✅ **Create** working code examples that demonstrate data type usage
- ✅ **Explain** technical concepts in beginner-friendly language
- ✅ **Connect** fundamental programming concepts to practical applications

@ -6,7 +6,11 @@
## Pre-Lecture Quiz
[Pre-lecture quiz](https://ff-quizzes.netlify.app)
When we think about writing code, we always want to ensure our code is readable. While this sounds counterintuitive, code is read many more times than it's written. One core tool in a developer's toolbox to ensure maintainable code is the **function**.
Writing the same code repeatedly is one of programming's most common frustrations. Functions solve this problem by letting you package code into reusable blocks. Think of functions like the standardized parts that made Henry Ford's assembly line revolutionary once you create a reliable component, you can use it wherever needed without rebuilding from scratch.
Functions allow you to bundle pieces of code so you can reuse them throughout your program. Instead of copying and pasting the same logic everywhere, you can create a function once and call it whenever needed. This approach keeps your code organized and makes updates much easier.
In this lesson, you'll learn how to create your own functions, pass information to them, and get useful results back. You'll discover the difference between functions and methods, learn modern syntax approaches, and see how functions can work with other functions. We'll build these concepts step by step.
[![Methods and Functions](https://img.youtube.com/vi/XgKsD6Zwvlc/0.jpg)](https://youtube.com/watch?v=XgKsD6Zwvlc "Methods and Functions")
@ -16,13 +20,15 @@ When we think about writing code, we always want to ensure our code is readable.
## Functions
At its core, a function is a block of code we can execute on demand. This is perfect for scenarios where we need to perform the same task multiple times; rather than duplicating the logic in multiple locations (which would make it hard to update when the time comes), we can centralize it in one location, and call it whenever we need the operation performed - you can even call functions from other functions!.
A function is a self-contained block of code that performs a specific task. It encapsulates logic that you can execute whenever needed.
Instead of writing the same code multiple times throughout your program, you can package it in a function and call that function whenever you need it. This approach keeps your code clean and makes updates much easier. Consider the maintenance challenge if you needed to change logic that was scattered across 20 different locations in your codebase.
Just as important is the ability to name a function. While this might seem trivial, the name provides a quick way of documenting a section of code. You could think of this as a label on a button. If I click on a button which reads "Cancel timer", I know it's going to stop running the clock.
Naming your functions descriptively is essential. A well-named function communicates its purpose clearly when you see `cancelTimer()`, you immediately understand what it does, just as a clearly labeled button tells you exactly what will happen when you click it.
## Creating and calling a function
The syntax for a function looks like the following:
Let's examine how to create a function. The syntax follows a consistent pattern:
```javascript
function nameOfFunction() { // function definition
@ -30,7 +36,13 @@ function nameOfFunction() { // function definition
}
```
If I wanted to create a function to display a greeting, it might look like this:
Let's break this down:
- The `function` keyword tells JavaScript "Hey, I'm creating a function!"
- `nameOfFunction` is where you give your function a descriptive name
- The parentheses `()` are where you can add parameters (we'll get to that soon)
- The curly braces `{}` contain the actual code that runs when you call the function
Let's create a simple greeting function to see this in action:
```javascript
function displayGreeting() {
@ -38,28 +50,34 @@ function displayGreeting() {
}
```
Whenever we want to call (or invoke) our function, we use the name of the function followed by `()`. It's worth noting the fact our function can be defined before or after we decide to call it; the JavaScript compiler will find it for you.
This function prints "Hello, world!" to the console. Once you've defined it, you can use it as many times as needed.
To execute (or "call") your function, write its name followed by parentheses. JavaScript allows you to define your function before or after you call it the JavaScript engine will handle the execution order.
```javascript
// calling our function
displayGreeting();
```
> **NOTE:** There is a special type of function known as a **method**, which you've already been using! In fact, we saw this in our demo above when we used `console.log`. What makes a method different from a function is a method is attached to an object (`console` in our example), while a function is free floating. You will hear many developers use these terms interchangeably.
When you run this line, it executes all the code inside your `displayGreeting` function, displaying "Hello, world!" in your browser's console. You can call this function repeatedly.
> **Note:** You've been using **methods** throughout these lessons. `console.log()` is a method essentially a function that belongs to the `console` object. The key difference is that methods are attached to objects, while functions stand independently. Many developers use these terms interchangeably in casual conversation.
### Function best practices
There are a handful of best practices to keep in mind when creating functions
Here are a few tips to help you write great functions:
- As always, use descriptive names so you know what the function will do
- Use **camelCasing** to combine words
- Keep your functions focused on a specific task
- Give your functions clear, descriptive names your future self will thank you!
- Use **camelCasing** for multi-word names (like `calculateTotal` instead of `calculate_total`)
- Keep each function focused on doing one thing well
## Passing information to a function
To make a function more reusable you'll often want to pass information into it. If we consider our `displayGreeting` example above, it will only display **Hello, world!**. Not the most useful function one could create. If we want to make it a little more flexible, like allowing someone to specify the name of the person to greet, we can add a **parameter**. A parameter (also sometimes called an **argument**), is additional information sent to a function.
Our `displayGreeting` function is limited it can only display "Hello, world!" for everyone. Parameters allow us to make functions more flexible and useful.
Parameters are listed in the definition part within parenthesis and are comma separated like so:
**Parameters** act like placeholders where you can insert different values each time you use the function. This way, the same function can work with different information on each call.
You list parameters inside the parentheses when you define your function, separating multiple parameters with commas:
```javascript
function name(param, param2, param3) {
@ -67,7 +85,9 @@ function name(param, param2, param3) {
}
```
We can update our `displayGreeting` to accept a name and have that displayed.
Each parameter acts like a placeholder when someone calls your function, they'll provide actual values that get plugged into these spots.
Let's update our greeting function to accept someone's name:
```javascript
function displayGreeting(name) {
@ -76,16 +96,22 @@ function displayGreeting(name) {
}
```
When we want to call our function and pass in the parameter, we specify it in the parenthesis.
Notice how we're using backticks (`` ` ``) and `${}` to insert the name directly into our message this is called a template literal, and it's a really handy way to build strings with variables mixed in.
Now when we call our function, we can pass in any name:
```javascript
displayGreeting('Christopher');
// displays "Hello, Christopher!" when run
```
JavaScript takes the string `'Christopher'`, assigns it to the `name` parameter, and creates the personalized message "Hello, Christopher!"
## Default values
We can make our function even more flexible by adding more parameters. But what if we don't want to require every value be specified? Keeping with our greeting example, we could leave name as required (we need to know who we're greeting), but we want to allow the greeting itself to be customized as desired. If someone doesn't want to customize it, we provide a default value instead. To provide a default value to a parameter, we set it much in the same way we set a value for a variable - `parameterName = 'defaultValue'`. To see a full example:
What if we want to make some parameters optional? That's where default values come in handy!
Let's say we want people to be able to customize the greeting word, but if they don't specify one, we'll just use "Hello" as a fallback. You can set up default values by using the equals sign, just like setting a variable:
```javascript
function displayGreeting(name, salutation='Hello') {
@ -93,7 +119,9 @@ function displayGreeting(name, salutation='Hello') {
}
```
When we call the function, we can then decide if we want to set a value for `salutation`.
Here, `name` is still required, but `salutation` has a backup value of `'Hello'` if no one provides a different greeting.
Now we can call this function in two different ways:
```javascript
displayGreeting('Christopher');
@ -103,19 +131,23 @@ displayGreeting('Christopher', 'Hi');
// displays "Hi, Christopher"
```
In the first call, JavaScript uses the default "Hello" since we didn't specify a salutation. In the second call, it uses our custom "Hi" instead. This flexibility makes functions adaptable to different scenarios.
## Return values
Up until now the function we built will always output to the [console](https://developer.mozilla.org/docs/Web/API/console). Sometimes this can be exactly what we're looking for, especially when we create functions which will be calling other services. But what if I want to create a helper function to perform a calculation and provide the value back so I can use it elsewhere?
Our functions so far have just been printing messages to the console, but what if you want a function to calculate something and give you back the result?
We can do this by using a **return value**. A return value is returned by the function, and can be stored in a variable just the same as we could store a literal value such as a string or number.
That's where **return values** come in. Instead of just displaying something, a function can hand you back a value that you can store in a variable or use in other parts of your code.
If a function does return something then the keyword `return` is used. The `return` keyword expects a value or reference of what's being returned like so:
To send a value back, you use the `return` keyword followed by whatever you want to return:
```javascript
return myVariable;
```
```
We could create a function to create a greeting message and return the value back to the caller
Here's something important: when a function hits a `return` statement, it immediately stops running and sends that value back to whoever called it.
Let's modify our greeting function to return the message instead of printing it:
```javascript
function createGreetingMessage(name) {
@ -124,19 +156,25 @@ function createGreetingMessage(name) {
}
```
When calling this function we'll store the value in a variable. This is much the same way we'd set a variable to a static value (like `const name = 'Christopher'`).
Now instead of printing the greeting, this function creates the message and hands it back to us.
To use the returned value, we can store it in a variable just like any other value:
```javascript
const greetingMessage = createGreetingMessage('Christopher');
```
Now `greetingMessage` contains "Hello, Christopher" and we can use it anywhere in our code to display it on a webpage, include it in an email, or pass it to another function.
## Functions as parameters for functions
As you progress in your programming career, you will come across functions which accept functions as parameters. This neat trick is commonly used when we don't know when something is going to occur or complete, but we know we need to perform an operation in response.
Functions can be passed as parameters to other functions. While this concept may seem complex initially, it's a powerful feature that enables flexible programming patterns.
As an example, consider [setTimeout](https://developer.mozilla.org/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout), which begins a timer and will execute code when it completes. We need to tell it what code we want to execute. Sounds like a perfect job for a function!
This pattern is super common when you want to say "when something happens, do this other thing." For example, "when the timer finishes, run this code" or "when the user clicks the button, call this function."
If you run the code below, after 3 seconds you'll see the message **3 seconds has elapsed**.
Let's look at `setTimeout`, which is a built-in function that waits a certain amount of time and then runs some code. We need to tell it what code to run perfect use case for passing a function!
Try this code after 3 seconds, you'll see a message:
```javascript
function displayDone() {
@ -146,13 +184,15 @@ function displayDone() {
setTimeout(displayDone, 3000);
```
Notice how we pass `displayDone` (without parentheses) to `setTimeout`. We're not calling the function ourselves we're handing it over to `setTimeout` and saying "call this in 3 seconds."
### Anonymous functions
Let's take another look at what we've built. We're creating a function with a name which will be used one time. As our application gets more complex, we can see ourselves creating a lot of functions which will only be called once. This isn't ideal. As it turns out, we don't always need to provide a name!
Sometimes you need a function for just one thing and don't want to give it a name. Think about it if you're only using a function once, why clutter up your code with an extra name?
When we are passing a function as a parameter we can bypass creating one in advance and instead build one as part of the parameter. We use the same `function` keyword, but instead we build it as a parameter.
JavaScript lets you create **anonymous functions** functions without names that you can define right where you need them.
Let's rewrite the code above to use an anonymous function:
Here's how we can rewrite our timer example using an anonymous function:
```javascript
setTimeout(function() {
@ -160,13 +200,15 @@ setTimeout(function() {
}, 3000);
```
If you run our new code you'll notice we get the same results. We've created a function, but didn't have to give it a name!
This achieves the same result, but the function is defined directly within the `setTimeout` call, eliminating the need for a separate function declaration.
### Fat arrow functions
One shortcut common in a lot of programming languages (including JavaScript) is the ability to use what's called an **arrow** or **fat arrow** function. It uses a special indicator of `=>`, which looks like an arrow - thus the name! By using `=>`, we are able to skip the `function` keyword.
Modern JavaScript has an even shorter way to write functions called **arrow functions**. They use `=>` (which looks like an arrow get it?) and are super popular with developers.
Arrow functions let you skip the `function` keyword and write more concise code.
Let's rewrite our code one more time to use a fat arrow function:
Here's our timer example using an arrow function:
```javascript
setTimeout(() => {
@ -174,9 +216,11 @@ setTimeout(() => {
}, 3000);
```
The `()` is where parameters would go (empty in this case), then comes the arrow `=>`, and finally the function body in curly braces. This provides the same functionality with more concise syntax.
### When to use each strategy
You've now seen we have three ways to pass a function as a parameter and might be wondering when to use each. If you know you'll be using the function more than once, create it as normal. If you'll be using it for just the one location, it's generally best to use an anonymous function. Whether or not you use a fat arrow function or the more traditional `function` syntax is up to you, but you will notice most modern developers prefer `=>`.
When should you use each approach? A practical guideline: if you'll use the function multiple times, give it a name and define it separately. If it's for one specific use, consider an anonymous function. Both arrow functions and traditional syntax are valid choices, though arrow functions are prevalent in modern JavaScript codebases.
---
@ -199,6 +243,8 @@ Use the Agent mode to complete the following challenge:
4. A function `calculate` that accepts another function as a parameter and two numbers, then applies the function to those numbers
5. Demonstrate calling each function with appropriate test cases
Learn more about [agent mode](https://code.visualstudio.com/blogs/2025/02/24/introducing-copilot-agent-mode) here.
## Post-Lecture Quiz
[Post-lecture quiz](https://ff-quizzes.netlify.app)

@ -2,12 +2,65 @@
## Instructions
Create different functions, both functions that return something and functions that don't return anything.
In this assignment, you'll practice creating different types of functions to reinforce the concepts you've learned about JavaScript functions, parameters, default values, and return statements.
See if you can create a function that has a mix of parameters and parameters with default values.
Create a JavaScript file called `functions-practice.js` and implement the following functions:
### Part 1: Basic Functions
1. **Create a function called `sayHello`** that doesn't take any parameters and simply logs "Hello!" to the console.
2. **Create a function called `introduceYourself`** that takes a `name` parameter and logs a message like "Hi, my name is [name]" to the console.
### Part 2: Functions with Default Parameters
3. **Create a function called `greetPerson`** that takes two parameters: `name` (required) and `greeting` (optional, defaults to "Hello"). The function should log a message like "[greeting], [name]!" to the console.
### Part 3: Functions that Return Values
4. **Create a function called `addNumbers`** that takes two parameters (`num1` and `num2`) and returns their sum.
5. **Create a function called `createFullName`** that takes `firstName` and `lastName` parameters and returns the full name as a single string.
### Part 4: Mix It All Together
6. **Create a function called `calculateTip`** that takes two parameters: `billAmount` (required) and `tipPercentage` (optional, defaults to 15). The function should calculate and return the tip amount.
### Part 5: Test Your Functions
Add function calls to test each of your functions and display the results using `console.log()`.
**Example test calls:**
```javascript
// Test your functions here
sayHello();
introduceYourself("Sarah");
greetPerson("Alex");
greetPerson("Maria", "Hi");
const sum = addNumbers(5, 3);
console.log(`The sum is: ${sum}`);
const fullName = createFullName("John", "Doe");
console.log(`Full name: ${fullName}`);
const tip = calculateTip(50);
console.log(`Tip for $50 bill: $${tip}`);
```
## Rubric
| Criteria | Exemplary | Adequate | Needs Improvement |
| -------- | -------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | ----------------- |
| | Solution is offered with two or more well-performing functions with diverse parameters | Working solution is offered with one function and few parameters | Solution has bugs |
| Criteria | Exemplary | Adequate | Needs Improvement |
| -------- | --------- | -------- | ----------------- |
| **Function Creation** | All 6 functions are correctly implemented with proper syntax and naming conventions | 4-5 functions are correctly implemented with minor syntax issues | 3 or fewer functions implemented or major syntax errors |
| **Parameters & Default Values** | Correctly uses required parameters, optional parameters, and default values as specified | Uses parameters correctly but may have issues with default values | Incorrect or missing parameter implementation |
| **Return Values** | Functions that should return values do so correctly, and functions that shouldn't return values only perform actions | Most return values are correct with minor issues | Significant problems with return statements |
| **Code Quality** | Clean, well-organized code with meaningful variable names and proper indentation | Code works but could be cleaner or better organized | Code is difficult to read or poorly structured |
| **Testing** | All functions are tested with appropriate function calls and results are displayed clearly | Most functions are tested adequately | Limited or incorrect testing of functions |
## Bonus Challenges (Optional)
If you want to challenge yourself further:
1. **Create an arrow function version** of one of your functions
2. **Create a function that accepts another function as a parameter** (like the `setTimeout` examples from the lesson)
3. **Add input validation** to ensure your functions handle invalid inputs gracefully
---
> 💡 **Tip**: Remember to open your browser's developer console (F12) to see the output of your `console.log()` statements!

@ -4,11 +4,17 @@
> Sketchnote by [Tomomi Imura](https://twitter.com/girlie_mac)
Have you ever wondered how applications make smart decisions? Like how a navigation system chooses the fastest route, or how a thermostat decides when to turn on the heat? This is the fundamental concept of decision-making in programming.
Just as Charles Babbage's Analytical Engine was designed to follow different sequences of operations based on conditions, modern JavaScript programs need to make choices based on varying circumstances. This ability to branch and make decisions is what transforms static code into responsive, intelligent applications.
In this lesson, you'll learn how to implement conditional logic in your programs. We'll explore conditional statements, comparison operators, and logical expressions that allow your code to evaluate situations and respond appropriately.
## Pre-Lecture Quiz
[Pre-lecture quiz](https://ff-quizzes.netlify.app/web/quiz/11)
Making decisions and controlling the order in which your code runs makes your code reusable and robust. This section covers the syntax for controlling data flow in JavaScript and its significance when used with Boolean data types
The ability to make decisions and control program flow is a fundamental aspect of programming. This section covers how to control the execution path of your JavaScript programs using Boolean values and conditional logic.
[![Making Decisions](https://img.youtube.com/vi/SxTp8j-fMMY/0.jpg)](https://youtube.com/watch?v=SxTp8j-fMMY "Making Decisions")
@ -18,18 +24,26 @@ Making decisions and controlling the order in which your code runs makes your co
## A Brief Recap on Booleans
Booleans can have only two values: `true` or `false`. Booleans help make decisions on which lines of code should run when certain conditions are met.
Before exploring decision-making, let's revisit Boolean values from our previous lesson. Named after mathematician George Boole, these values represent binary states either `true` or `false`. There's no ambiguity, no middle ground.
These binary values form the foundation of all computational logic. Every decision your program makes ultimately reduces to a Boolean evaluation.
Set your boolean to be true or false like this:
Creating Boolean variables is straightforward:
```javascript
let myTrueBool = true;
let myFalseBool = false;
```
`let myTrueBool = true`
`let myFalseBool = false`
This creates two variables with explicit Boolean values.
✅ Booleans are named after the English mathematician, philosopher and logician George Boole (18151864).
## Comparison Operators and Booleans
Operators are used to evaluate conditions by making comparisons that will create a Boolean value. The following is a list of operators that are frequently used.
In practice, you'll rarely set Boolean values manually. Instead, you'll generate them by evaluating conditions: "Is this number greater than that one?" or "Are these values equal?"
Comparison operators enable these evaluations. They compare values and return Boolean results based on the relationship between the operands.
| Symbol | Description | Example |
| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
@ -44,48 +58,60 @@ Operators are used to evaluate conditions by making comparisons that will create
## If Statement
The if statement will run code in between its blocks if the condition is true.
The `if` statement is like asking a question in your code. "If this condition is true, then do this thing." It's probably the most important tool you'll use for making decisions in JavaScript.
Here's how it works:
```javascript
if (condition) {
//Condition is true. Code in this block will run.
// Condition is true. Code in this block will run.
}
```
Logical operators are often used to form the condition.
The condition goes inside the parentheses, and if it's `true`, JavaScript runs the code inside the curly braces. If it's `false`, JavaScript just skips that whole block.
You'll often use comparison operators to create these conditions. Let's see a practical example:
```javascript
let currentMoney;
let laptopPrice;
let currentMoney = 1000;
let laptopPrice = 800;
if (currentMoney >= laptopPrice) {
//Condition is true. Code in this block will run.
// Condition is true. Code in this block will run.
console.log("Getting a new laptop!");
}
```
Since `1000 >= 800` evaluates to `true`, the code inside the block executes, displaying "Getting a new laptop!" in the console.
## If..Else Statement
The `else` statement will run the code in between its blocks when the condition is false. It's optional with an `if` statement.
But what if you want your program to do something different when the condition is false? That's where `else` comes in it's like having a backup plan.
The `else` statement gives you a way to say "if this condition isn't true, do this other thing instead."
```javascript
let currentMoney;
let laptopPrice;
let currentMoney = 500;
let laptopPrice = 800;
if (currentMoney >= laptopPrice) {
//Condition is true. Code in this block will run.
// Condition is true. Code in this block will run.
console.log("Getting a new laptop!");
} else {
//Condition is false. Code in this block will run.
// Condition is false. Code in this block will run.
console.log("Can't afford a new laptop, yet!");
}
```
Now since `500 >= 800` is `false`, JavaScript skips the first block and runs the `else` block instead. You'll see "Can't afford a new laptop, yet!" in the console.
✅ Test your understanding of this code and the following code by running it in a browser console. Change the values of the currentMoney and laptopPrice variables to change the returned `console.log()`.
## Switch Statement
The `switch` statement is used to perform different actions based on different conditions. Use the `switch` statement to select one of many code blocks to be executed.
Sometimes you need to compare one value against multiple options. While you could chain several `if..else` statements, this approach becomes unwieldy. The `switch` statement provides a cleaner structure for handling multiple discrete values.
The concept resembles the mechanical switching systems used in early telephone exchanges one input value determines which specific path the execution follows.
```javascript
switch (expression) {
@ -96,61 +122,83 @@ switch (expression) {
// code block
break;
default:
// code block
// code block
}
```
Here's how it's structured:
- JavaScript evaluates the expression once
- It looks through each `case` to find a match
- When it finds a match, it runs that code block
- The `break` tells JavaScript to stop and exit the switch
- If no cases match, it runs the `default` block (if you have one)
```javascript
// program using switch statement
let a = 2;
// Program using switch statement for day of week
let dayNumber = 2;
let dayName;
switch (a) {
switch (dayNumber) {
case 1:
a = "one";
dayName = "Monday";
break;
case 2:
a = "two";
dayName = "Tuesday";
break;
case 3:
dayName = "Wednesday";
break;
default:
a = "not found";
dayName = "Unknown day";
break;
}
console.log(`The value is ${a}`);
console.log(`Today is ${dayName}`);
```
In this example, JavaScript sees that `dayNumber` is `2`, finds the matching `case 2`, sets `dayName` to "Tuesday", and then breaks out of the switch. The result? "Today is Tuesday" gets logged to the console.
✅ Test your understanding of this code and the following code by running it in a browser console. Change the values of the variable a to change the returned `console.log()`.
## Logical Operators and Booleans
Decisions might require more than one comparison, and can be strung together with logical operators to produce a Boolean value.
Complex decisions often require evaluating multiple conditions simultaneously. Just as Boolean algebra allows mathematicians to combine logical expressions, programming provides logical operators to connect multiple Boolean conditions.
These operators enable sophisticated conditional logic by combining simple true/false evaluations.
| Symbol | Description | Example |
| ------ | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| `&&` | **Logical AND**: Compares two Boolean expressions. Returns true **only** if both sides are true | `(5 > 6) && (5 < 6 ) //One side is false, other is true. Returns false` |
| `\|\|` | **Logical OR**: Compares two Boolean expressions. Returns true if at least one side is true | `(5 > 6) \|\| (5 < 6) //One side is false, other is true. Returns true` |
| `!` | **Logical NOT**: Returns the opposite value of a Boolean expression | `!(5 > 6) // 5 is not greater than 6, but "!" will return true` |
| `&&` | **Logical AND**: Compares two Boolean expressions. Returns true **only** if both sides are true | `(5 > 3) && (5 < 10) // Both sides are true. Returns true` |
| `\|\|` | **Logical OR**: Compares two Boolean expressions. Returns true if at least one side is true | `(5 > 10) \|\| (5 < 10) // One side is false, other is true. Returns true` |
| `!` | **Logical NOT**: Returns the opposite value of a Boolean expression | `!(5 > 10) // 5 is not greater than 10, so "!" makes it true` |
These operators let you combine conditions in useful ways:
- AND (`&&`) means both conditions must be true
- OR (`||`) means at least one condition must be true
- NOT (`!`) flips true to false (and vice versa)
## Conditions and Decisions with Logical Operators
Logical operators can be used to form conditions in if..else statements.
Let's see these logical operators in action with a more realistic example:
```javascript
let currentMoney;
let laptopPrice;
let laptopDiscountPrice = laptopPrice - laptopPrice * 0.2; //Laptop price at 20 percent off
let currentMoney = 600;
let laptopPrice = 800;
let laptopDiscountPrice = laptopPrice - (laptopPrice * 0.2); // Laptop price at 20 percent off
if (currentMoney >= laptopPrice || currentMoney >= laptopDiscountPrice) {
//Condition is true. Code in this block will run.
// Condition is true. Code in this block will run.
console.log("Getting a new laptop!");
} else {
//Condition is true. Code in this block will run.
// Condition is false. Code in this block will run.
console.log("Can't afford a new laptop, yet!");
}
```
### Negation operator
In this example: we calculate a 20% discount price (640), then evaluate whether our available funds cover either the full price OR the discounted price. Since 600 meets the discounted price threshold of 640, the condition evaluates to true.
You've seen so far how you can use an `if...else` statement to create conditional logic. Anything that goes into an `if` needs to evaluate to true/false. By using the `!` operator you can _negate_ the expression. It would look like so:
### Negation Operator
Sometimes it's easier to think about when something is NOT true. Like instead of asking "Is the user logged in?", you might want to ask "Is the user NOT logged in?" The exclamation mark (`!`) operator flips the logic for you.
```javascript
if (!condition) {
@ -160,14 +208,18 @@ if (!condition) {
}
```
### Ternary expressions
The `!` operator is like saying "the opposite of..." if something is `true`, `!` makes it `false`, and vice versa.
### Ternary Expressions
`if...else` isn't the only way to express decision logic. You can also use something called a ternary operator. The syntax for it looks like this:
For simple conditional assignments, JavaScript provides the **ternary operator**. This concise syntax allows you to write a conditional expression in a single line, useful when you need to assign one of two values based on a condition.
```javascript
let variable = condition ? <return this if true> : <return this if false>
let variable = condition ? returnThisIfTrue : returnThisIfFalse;
```
It reads like a question: "Is this condition true? If yes, use this value. If no, use that value."
Below is a more tangible example:
```javascript
@ -178,13 +230,9 @@ let biggestNumber = firstNumber > secondNumber ? firstNumber : secondNumber;
✅ Take a minute to read this code a few times. Do you understand how these operators are working?
The above states that
Here's what this line is saying: "Is `firstNumber` greater than `secondNumber`? If yes, put `firstNumber` in `biggestNumber`. If no, put `secondNumber` in `biggestNumber`."
- if `firstNumber` is larger than `secondNumber`
- then assign `firstNumber` to `biggestNumber`
- else assign `secondNumber`.
The ternary expression is just a compact way of writing the code below:
The ternary operator is just a shorter way to write this traditional `if..else` statement:
```javascript
let biggestNumber;
@ -195,6 +243,8 @@ if (firstNumber > secondNumber) {
}
```
Both approaches produce identical results. The ternary operator offers conciseness, while the traditional if-else structure may be more readable for complex conditions.
---
@ -227,6 +277,8 @@ Requirements:
Test your program with various scores including edge cases like 59, 60, 89, 90, and invalid inputs.
Learn more about [agent mode](https://code.visualstudio.com/blogs/2025/02/24/introducing-copilot-agent-mode) here.
## Post-Lecture Quiz

@ -1,40 +1,104 @@
# Operators
# Making Decisions: Student Grade Processor
## Instructions
## Learning Objectives
Play around with operators. Here's a suggestion for a program you can implement:
In this assignment, you'll practice the decision-making concepts from this lesson by building a program that processes student grades from different grading systems. You'll use `if...else` statements, comparison operators, and logical operators to determine which students pass their courses.
You have a set of students from two different grading systems.
## The Challenge
### First grading system
You work for a school that recently merged with another institution. Now you need to process student grades from two completely different grading systems and determine which students are passing. This is a perfect opportunity to practice conditional logic!
One grading system is defined as grades being from 1-5 where 3 and above means you pass the course.
### Understanding the Grading Systems
### Second grading system
#### First Grading System (Numeric)
- Grades are given as numbers from 1-5
- **Passing grade**: 3 and above (3, 4, or 5)
- **Failing grade**: Below 3 (1 or 2)
The other grade system has the following grades `A, A-, B, B-, C, C-` where `A` is the top grade and `C` is the lowest passing grade.
#### Second Grading System (Letter Grades)
- Grades use letters: `A`, `A-`, `B`, `B-`, `C`, `C-`
- **Passing grades**: `A`, `A-`, `B`, `B-`, `C`, `C-` (all listed grades are passing)
- **Note**: This system doesn't include failing grades like `D` or `F`
### The task
### Your Task
Given the following array `allStudents` representing all students and their grades, construct a new array `studentsWhoPass` containing all students who pass.
> TIP, use a for-loop and if...else and comparison operators:
Given the following array `allStudents` representing all students and their grades, construct a new array `studentsWhoPass` containing all students who pass according to their respective grading systems.
```javascript
let allStudents = [
'A',
'B-',
1,
4,
5,
2
]
'A', // Letter grade - passing
'B-', // Letter grade - passing
1, // Numeric grade - failing
4, // Numeric grade - passing
5, // Numeric grade - passing
2 // Numeric grade - failing
];
let studentsWhoPass = [];
```
### Step-by-Step Approach
1. **Set up a loop** to go through each grade in the `allStudents` array
2. **Check the grade type** (is it a number or a string?)
3. **Apply the appropriate grading system rules**:
- For numbers: check if grade >= 3
- For strings: check if it's one of the valid passing letter grades
4. **Add passing grades** to the `studentsWhoPass` array
### Helpful Code Techniques
Use these JavaScript concepts from the lesson:
- **typeof operator**: `typeof grade === 'number'` to check if it's a numeric grade
- **Comparison operators**: `>=` to compare numeric grades
- **Logical operators**: `||` to check multiple letter grade conditions
- **if...else statements**: to handle different grading systems
- **Array methods**: `.push()` to add passing grades to your new array
### Expected Output
When you run your program, `studentsWhoPass` should contain: `['A', 'B-', 4, 5]`
**Why these grades pass:**
- `'A'` and `'B-'` are valid letter grades (all letter grades in this system are passing)
- `4` and `5` are numeric grades >= 3
- `1` and `2` fail because they're numeric grades < 3
## Testing Your Solution
Test your code with different scenarios:
```javascript
// Test with different grade combinations
let testGrades1 = ['A-', 3, 'C', 1, 'B'];
let testGrades2 = [5, 'A', 2, 'C-', 4];
// Your solution should work with any combination of valid grades
```
## Bonus Challenges
Once you complete the basic assignment, try these extensions:
1. **Add validation**: Check for invalid grades (like negative numbers or invalid letters)
2. **Count statistics**: Calculate how many students pass vs. fail
3. **Grade conversion**: Convert all grades to a single numeric system (A=5, B=4, C=3, etc.)
## Rubric
| Criteria | Exemplary | Adequate | Needs Improvement |
| -------- | ------------------------------ | ----------------------------- | ------------------------------- |
| | Complete solution is presented | Partial solution is presented | Solution with bugs is presented |
| Criteria | Exemplary (4) | Proficient (3) | Developing (2) | Beginning (1) |
|----------|---------------|----------------|----------------|---------------|
| **Functionality** | Program correctly identifies all passing grades from both systems | Program works with minor issues or edge cases | Program partially works but has logical errors | Program has significant errors or doesn't run |
| **Code Structure** | Clean, well-organized code with proper if...else logic | Good structure with appropriate conditional statements | Acceptable structure with some organizational issues | Poor structure, difficult to follow logic |
| **Use of Concepts** | Effectively uses comparison operators, logical operators, and conditional statements | Good use of lesson concepts with minor gaps | Some use of lesson concepts but missing key elements | Limited use of lesson concepts |
| **Problem Solving** | Shows clear understanding of the problem and elegant solution approach | Good problem-solving approach with solid logic | Adequate problem-solving with some confusion | Unclear approach, doesn't demonstrate understanding |
## Submission Guidelines
1. **Test your code** thoroughly with the provided examples
2. **Add comments** explaining your logic, especially for the conditional statements
3. **Verify output** matches expected results: `['A', 'B-', 4, 5]`
4. **Consider edge cases** like empty arrays or unexpected data types
> 💡 **Pro Tip**: Start simple! Get the basic functionality working first, then add more sophisticated features. Remember, the goal is to practice decision-making logic with the tools you learned in this lesson.

@ -6,7 +6,11 @@
## Pre-Lecture Quiz
[Pre-lecture quiz](https://ff-quizzes.netlify.app/web/quiz/13)
This lesson covers the basics of JavaScript, the language that provides interactivity on the web. In this lesson, you'll learn about arrays and loops, which are used to manipulate data.
Ever wondered how websites keep track of shopping cart items or display your friend list? That's where arrays and loops come in. Arrays are like digital containers that hold multiple pieces of information, while loops let you work with all that data efficiently without repetitive code.
Together, these two concepts form the foundation for handling information in your programs. You'll learn to move from manually writing out every single step to creating smart, efficient code that can process hundreds or even thousands of items quickly.
By the end of this lesson, you'll understand how to accomplish complex data tasks with just a few lines of code. Let's explore these essential programming concepts.
[![Arrays](https://img.youtube.com/vi/1U4qTyq02Xw/0.jpg)](https://youtube.com/watch?v=1U4qTyq02Xw "Arrays")
@ -18,102 +22,325 @@ This lesson covers the basics of JavaScript, the language that provides interact
## Arrays
Working with data is a common task for any language, and it's a much easier task when data is organized in a structural format, such as arrays. With arrays, data is stored in a structure similar to a list. One major benefit of arrays is that you can store different types of data in one array.
Think of arrays as a digital filing cabinet - instead of storing one document per drawer, you can organize multiple related items in a single, structured container. In programming terms, arrays let you store multiple pieces of information in one organized package.
Whether you're building a photo gallery, managing a to-do list, or keeping track of high scores in a game, arrays provide the foundation for data organization. Let's see how they work.
✅ Arrays are all around us! Can you think of a real-life example of an array, such as a solar panel array?
The syntax for an array is a pair of square brackets.
### Creating Arrays
Creating an array is super simple - just use square brackets!
```javascript
let myArray = [];
// Empty array - like an empty shopping cart waiting for items
const myArray = [];
```
This is an empty array, but arrays can be declared already populated with data. Multiple values in an array are separated by a comma.
**What's happening here?**
You've just created an empty container using those square brackets `[]`. Think of it like an empty library shelf - it's ready to hold whatever books you want to organize there.
You can also fill your array with initial values right from the start:
```javascript
let iceCreamFlavors = ["Chocolate", "Strawberry", "Vanilla", "Pistachio", "Rocky Road"];
// Your ice cream shop's flavor menu
const iceCreamFlavors = ["Chocolate", "Strawberry", "Vanilla", "Pistachio", "Rocky Road"];
// A user's profile info (mixing different types of data)
const userData = ["John", 25, true, "developer"];
// Test scores for your favorite class
const scores = [95, 87, 92, 78, 85];
```
The array values are assigned a unique value called the **index**, a whole number that is assigned based on its distance from the beginning of the array. In the example above, the string value "Chocolate" has an index of 0, and the index of "Rocky Road" is 4. Use the index with square brackets to retrieve, change, or insert array values.
**Cool things to notice:**
- You can store text, numbers, or even true/false values in the same array
- Just separate each item with a comma - easy!
- Arrays are perfect for keeping related information together
### Array Indexing
Here's something that might seem unusual at first: arrays number their items starting from 0, not 1. This zero-based indexing has its roots in how computer memory works - it's been a programming convention since the early days of computing languages like C. Each spot in the array gets its own address number called an **index**.
| Index | Value | Description |
|-------|-------|-------------|
| 0 | "Chocolate" | First element |
| 1 | "Strawberry" | Second element |
| 2 | "Vanilla" | Third element |
| 3 | "Pistachio" | Fourth element |
| 4 | "Rocky Road" | Fifth element |
✅ Does it surprise you that arrays start at the zero index? In some programming languages, indexes start at 1. There's an interesting history around this, which you can [read on Wikipedia](https://en.wikipedia.org/wiki/Zero-based_numbering).
**Accessing Array Elements:**
```javascript
let iceCreamFlavors = ["Chocolate", "Strawberry", "Vanilla", "Pistachio", "Rocky Road"];
iceCreamFlavors[2]; //"Vanilla"
const iceCreamFlavors = ["Chocolate", "Strawberry", "Vanilla", "Pistachio", "Rocky Road"];
// Access individual elements using bracket notation
console.log(iceCreamFlavors[0]); // "Chocolate" - first element
console.log(iceCreamFlavors[2]); // "Vanilla" - third element
console.log(iceCreamFlavors[4]); // "Rocky Road" - last element
```
You can leverage the index to change a value, like this:
**Breaking down what happens here:**
- **Uses** square bracket notation with the index number to access elements
- **Returns** the value stored at that specific position in the array
- **Starts** counting from 0, making the first element index 0
**Modifying Array Elements:**
```javascript
iceCreamFlavors[4] = "Butter Pecan"; //Changed "Rocky Road" to "Butter Pecan"
// Change an existing value
iceCreamFlavors[4] = "Butter Pecan";
console.log(iceCreamFlavors[4]); // "Butter Pecan"
// Add a new element at the end
iceCreamFlavors[5] = "Cookie Dough";
console.log(iceCreamFlavors[5]); // "Cookie Dough"
```
And you can insert a new value at a given index like this:
**In the above, we've:**
- **Modified** the element at index 4 from "Rocky Road" to "Butter Pecan"
- **Added** a new element "Cookie Dough" at index 5
- **Expanded** the array length automatically when adding beyond current bounds
### Array Length and Common Methods
Arrays come with built-in properties and methods that make working with data much easier.
**Finding Array Length:**
```javascript
iceCreamFlavors[5] = "Cookie Dough"; //Added "Cookie Dough"
const iceCreamFlavors = ["Chocolate", "Strawberry", "Vanilla", "Pistachio", "Rocky Road"];
console.log(iceCreamFlavors.length); // 5
// Length updates automatically as array changes
iceCreamFlavors.push("Mint Chip");
console.log(iceCreamFlavors.length); // 6
```
✅ A more common way to push values to an array is by using array operators such as array.push()
**Key points to remember:**
- **Returns** the total number of elements in the array
- **Updates** automatically when elements are added or removed
- **Provides** a dynamic count useful for loops and validation
To find out how many items are in an array, use the `length` property.
**Essential Array Methods:**
```javascript
let iceCreamFlavors = ["Chocolate", "Strawberry", "Vanilla", "Pistachio", "Rocky Road"];
iceCreamFlavors.length; //5
const fruits = ["apple", "banana", "orange"];
// Add elements
fruits.push("grape"); // Adds to end: ["apple", "banana", "orange", "grape"]
fruits.unshift("strawberry"); // Adds to beginning: ["strawberry", "apple", "banana", "orange", "grape"]
// Remove elements
const lastFruit = fruits.pop(); // Removes and returns "grape"
const firstFruit = fruits.shift(); // Removes and returns "strawberry"
// Find elements
const index = fruits.indexOf("banana"); // Returns 1 (position of "banana")
const hasApple = fruits.includes("apple"); // Returns true
```
**Understanding these methods:**
- **Adds** elements with `push()` (end) and `unshift()` (beginning)
- **Removes** elements with `pop()` (end) and `shift()` (beginning)
- **Locates** elements with `indexOf()` and checks existence with `includes()`
- **Returns** useful values like removed elements or position indexes
✅ Try it yourself! Use your browser's console to create and manipulate an array of your own creation.
## Loops
Loops allow us to perform repetitive or **iterative** tasks, and can save a lot of time and code. Each iteration can vary in their variables, values, and conditions. There are different types of loops in JavaScript, and they all have small differences, but essentially do the same thing: loop over data.
Think of the famous punishment from Charles Dickens' novels where students had to write lines repeatedly on a slate. Imagine if you could simply instruct someone to "write this sentence 100 times" and have it done automatically. That's exactly what loops do for your code.
Loops are like having a tireless assistant who can repeat tasks without error. Whether you need to check every item in a shopping cart or display all the photos in an album, loops handle the repetition efficiently.
JavaScript provides several types of loops to choose from. Let's examine each one and understand when to use them.
### For Loop
The `for` loop requires 3 parts to iterate:
- `counter` A variable that is typically initialized with a number that counts the number of iterations
- `condition` Expression that uses comparison operators to cause the loop to stop when `false`
- `iteration-expression` Runs at the end of each iteration, typically used to change the counter value
The `for` loop is like setting a timer - you know exactly how many times you want something to happen. It's super organized and predictable, which makes it perfect when you're working with arrays or need to count things.
**For Loop Structure:**
| Component | Purpose | Example |
|-----------|---------|----------|
| **Initialization** | Sets starting point | `let i = 0` |
| **Condition** | When to continue | `i < 10` |
| **Increment** | How to update | `i++` |
```javascript
// Counting up to 10
// Counting from 0 to 9
for (let i = 0; i < 10; i++) {
console.log(i);
console.log(`Count: ${i}`);
}
// More practical example: processing scores
const testScores = [85, 92, 78, 96, 88];
for (let i = 0; i < testScores.length; i++) {
console.log(`Student ${i + 1}: ${testScores[i]}%`);
}
```
**Step by step, here's what's happening:**
- **Initializes** the counter variable `i` to 0 at the start
- **Checks** the condition `i < 10` before each iteration
- **Executes** the code block when the condition is true
- **Increments** `i` by 1 after each iteration with `i++`
- **Stops** when the condition becomes false (when `i` reaches 10)
✅ Run this code in a browser console. What happens when you make small changes to the counter, condition, or iteration expression? Can you make it run backwards, creating a countdown?
### While loop
### While Loop
The `while` loop is like saying "keep doing this until..." - you might not know exactly how many times it'll run, but you know when to stop. It's perfect for things like asking a user for input until they give you what you need, or searching through data until you find what you're looking for.
Unlike the syntax of the `for` loop, `while` loops only require a condition that will stop the loop when the condition becomes `false`. Conditions in loops usually rely on other values like counters, and must be managed during the loop. Starting values for counters must be created outside the loop, and any expressions to meet a condition, including changing the counter must be maintained inside the loop.
**While Loop Characteristics:**
- **Continues** executing as long as the condition is true
- **Requires** manual management of any counter variables
- **Checks** the condition before each iteration
- **Risks** infinite loops if the condition never becomes false
```javascript
//Counting up to 10
// Basic counting example
let i = 0;
while (i < 10) {
console.log(i);
i++;
console.log(`While count: ${i}`);
i++; // Don't forget to increment!
}
// More practical example: processing user input
let userInput = "";
let attempts = 0;
const maxAttempts = 3;
while (userInput !== "quit" && attempts < maxAttempts) {
userInput = prompt(`Enter 'quit' to exit (attempt ${attempts + 1}):`);
attempts++;
}
if (attempts >= maxAttempts) {
console.log("Maximum attempts reached!");
}
```
**Understanding these examples:**
- **Manages** the counter variable `i` manually inside the loop body
- **Increments** the counter to prevent infinite loops
- **Demonstrates** practical use case with user input and attempt limiting
- **Includes** safety mechanisms to prevent endless execution
### Modern Loop Alternatives
JavaScript offers modern loop syntax that can make your code more readable and less error-prone.
**For...of Loop (ES6+):**
```javascript
const colors = ["red", "green", "blue", "yellow"];
// Modern approach - cleaner and safer
for (const color of colors) {
console.log(`Color: ${color}`);
}
// Compare with traditional for loop
for (let i = 0; i < colors.length; i++) {
console.log(`Color: ${colors[i]}`);
}
```
**Key advantages of for...of:**
- **Eliminates** index management and potential off-by-one errors
- **Provides** direct access to array elements
- **Improves** code readability and reduces syntax complexity
**forEach Method:**
```javascript
const prices = [9.99, 15.50, 22.75, 8.25];
// Using forEach for functional programming style
prices.forEach((price, index) => {
console.log(`Item ${index + 1}: $${price.toFixed(2)}`);
});
// forEach with arrow functions for simple operations
prices.forEach(price => console.log(`Price: $${price}`));
```
**What you need to know about forEach:**
- **Executes** a function for each array element
- **Provides** both element value and index as parameters
- **Cannot** be stopped early (unlike traditional loops)
- **Returns** undefined (doesn't create a new array)
✅ Why would you choose a for loop vs. a while loop? 17K viewers had the same question on StackOverflow, and some of the opinions [might be interesting to you](https://stackoverflow.com/questions/39969145/while-loops-vs-for-loops-in-javascript).
## Loops and Arrays
Arrays are often used with loops because most conditions require the length of the array to stop the loop, and the index can also be the counter value.
Combining arrays with loops creates powerful data processing capabilities. This pairing is fundamental to many programming tasks, from displaying lists to calculating statistics.
**Traditional Array Processing:**
```javascript
let iceCreamFlavors = ["Chocolate", "Strawberry", "Vanilla", "Pistachio", "Rocky Road"];
const iceCreamFlavors = ["Chocolate", "Strawberry", "Vanilla", "Pistachio", "Rocky Road"];
// Classic for loop approach
for (let i = 0; i < iceCreamFlavors.length; i++) {
console.log(iceCreamFlavors[i]);
} //Ends when all flavors are printed
console.log(`Flavor ${i + 1}: ${iceCreamFlavors[i]}`);
}
// Modern for...of approach
for (const flavor of iceCreamFlavors) {
console.log(`Available flavor: ${flavor}`);
}
```
**Let's understand each approach:**
- **Uses** array length property to determine loop boundary
- **Accesses** elements by index in traditional for loops
- **Provides** direct element access in for...of loops
- **Processes** each array element exactly once
**Practical Data Processing Example:**
```javascript
const studentGrades = [85, 92, 78, 96, 88, 73, 89];
let total = 0;
let highestGrade = studentGrades[0];
let lowestGrade = studentGrades[0];
// Process all grades with a single loop
for (let i = 0; i < studentGrades.length; i++) {
const grade = studentGrades[i];
total += grade;
if (grade > highestGrade) {
highestGrade = grade;
}
if (grade < lowestGrade) {
lowestGrade = grade;
}
}
const average = total / studentGrades.length;
console.log(`Average: ${average.toFixed(1)}`);
console.log(`Highest: ${highestGrade}`);
console.log(`Lowest: ${lowestGrade}`);
```
✅ Experiment with looping over an array of your own making in your browser's console.
**Here's how this code works:**
- **Initializes** tracking variables for sum and extremes
- **Processes** each grade with a single efficient loop
- **Accumulates** the total for average calculation
- **Tracks** highest and lowest values during iteration
- **Calculates** final statistics after loop completion
✅ Experiment with looping over an array of your own making in your browser's console.
---
@ -121,13 +348,17 @@ for (let i = 0; i < iceCreamFlavors.length; i++) {
Use the Agent mode to complete the following challenge:
**Description:** Build a data processing function that combines arrays and loops to analyze a dataset.
**Description:** Build a comprehensive data processing function that combines arrays and loops to analyze a dataset and generate meaningful insights.
**Prompt:** Create a function called `analyzeGrades` that takes an array of student grade objects (each containing name and score properties) and returns statistics including the highest score, lowest score, average score, and count of students who passed (score >= 70). Use loops to process the data.
**Prompt:** Create a function called `analyzeGrades` that takes an array of student grade objects (each containing name and score properties) and returns an object with statistics including the highest score, lowest score, average score, count of students who passed (score >= 70), and an array of student names who scored above average. Use at least two different loop types in your solution.
Learn more about [agent mode](https://code.visualstudio.com/blogs/2025/02/24/introducing-copilot-agent-mode) here.
## 🚀 Challenge
There are other ways of looping over arrays other than for and while loops. There are [forEach](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach), [for-of](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/for...of), and [map](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/map). Rewrite your array loop using one of these techniques.
JavaScript offers several modern array methods that can replace traditional loops for specific tasks. Explore [forEach](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach), [for-of](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/for...of), [map](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/map), [filter](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/filter), and [reduce](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce).
**Your challenge:** Refactor the student grades example using at least three different array methods. Notice how much cleaner and more readable the code becomes with modern JavaScript syntax.
## Post-Lecture Quiz
[Post-lecture quiz](https://ff-quizzes.netlify.app/web/quiz/14)

@ -1,13 +1,108 @@
# Loop an Array
# Arrays and Loops Assignment
## Instructions
Complete the following exercises to practice working with arrays and loops. Each exercise builds on concepts from the lesson and encourages you to apply different loop types and array methods.
### Exercise 1: Number Pattern Generator
Create a program that lists every 3rd number between 1-20 and prints it to the console.
> TIP: use a for-loop and modify the iteration-expression
**Requirements:**
- Use a `for` loop with a custom increment
- Display numbers in a user-friendly format
- Add descriptive comments explaining your logic
**Expected Output:**
```
3, 6, 9, 12, 15, 18
```
> **Tip:** Modify the iteration-expression in your for loop to skip numbers.
### Exercise 2: Array Analysis
Create an array of at least 8 different numbers and write functions to analyze the data.
**Requirements:**
- Create an array called `numbers` with at least 8 values
- Write a function `findMaximum()` that returns the highest number
- Write a function `findMinimum()` that returns the lowest number
- Write a function `calculateSum()` that returns the total of all numbers
- Test each function and display the results
**Bonus Challenge:** Create a function that finds the second highest number in the array.
### Exercise 3: String Array Processing
Create an array of your favorite movies/books/songs and practice different loop types.
**Requirements:**
- Create an array with at least 5 string values
- Use a traditional `for` loop to display items with numbers (1. Item Name)
- Use a `for...of` loop to display items in uppercase
- Use `forEach()` method to count and display the total characters
**Example Output:**
```
Traditional for loop:
1. The Matrix
2. Inception
3. Interstellar
For...of loop (uppercase):
THE MATRIX
INCEPTION
INTERSTELLAR
Character count:
Total characters across all titles: 42
```
### Exercise 4: Data Filtering (Advanced)
Create a program that processes an array of objects representing students.
**Requirements:**
- Create an array of at least 5 student objects with properties: `name`, `age`, `grade`
- Use loops to find students who are 18 or older
- Calculate the average grade of all students
- Create a new array containing only students with grades above 85
**Example Structure:**
```javascript
const students = [
{ name: "Alice", age: 17, grade: 92 },
{ name: "Bob", age: 18, grade: 84 },
// Add more students...
];
```
## Testing Your Code
Test your programs by:
1. Running each exercise in your browser's console
2. Verifying outputs match expected results
3. Testing with different data sets
4. Checking that your code handles edge cases (empty arrays, single elements)
## Submission Guidelines
Include the following in your submission:
- Well-commented JavaScript code for each exercise
- Screenshots or text output showing your programs running
- Brief explanation of which loop type you chose for each task and why
## Rubric
| Criteria | Exemplary | Adequate | Needs Improvement |
| -------- | --------------------------------------- | ------------------------ | ------------------------------ |
| | Program runs correctly and is commented | Program is not commented | Program is incomplete or buggy |
| Criteria | Exemplary (3 points) | Adequate (2 points) | Needs Improvement (1 point) |
| -------- | -------------------- | ------------------- | --------------------------- |
| **Functionality** | All exercises completed correctly with bonus challenges | All required exercises work correctly | Some exercises incomplete or contain errors |
| **Code Quality** | Clean, well-organized code with descriptive variable names | Code works but could be cleaner | Code is messy or hard to understand |
| **Comments** | Comprehensive comments explaining logic and decisions | Basic comments present | Minimal or no comments |
| **Loop Usage** | Demonstrates understanding of different loop types appropriately | Uses loops correctly but limited variety | Incorrect or inefficient loop usage |
| **Testing** | Evidence of thorough testing with multiple scenarios | Basic testing demonstrated | Little evidence of testing |
## Reflection Questions
After completing the exercises, consider:
1. Which type of loop felt most natural to use and why?
2. What challenges did you encounter when working with arrays?
3. How could these skills apply to real-world web development projects?
4. What would you do differently if you had to optimize your code for performance?

@ -3,74 +3,87 @@
![Introduction to HTML](../../sketchnotes/webdev101-html.png)
> Sketchnote by [Tomomi Imura](https://twitter.com/girlie_mac)
## Pre-Lecture Quiz
[Pre-lecture quiz](https://ff-quizzes.netlify.app/web/quiz/15)
HTML, or HyperText Markup Language, is the foundation of every website you've ever visited. Think of HTML as the skeleton that gives structure to web pages it defines where content goes, how it's organized, and what each piece represents. While CSS will later "dress up" your HTML with colors and layouts, and JavaScript will bring it to life with interactivity, HTML provides the essential structure that makes everything else possible.
In this lesson, you'll create the HTML structure for a virtual terrarium interface. This hands-on project will teach you fundamental HTML concepts while building something visually engaging. You'll learn how to organize content using semantic elements, work with images, and create the foundation for an interactive web application.
> Check out video
By the end of this lesson, you'll have a working HTML page displaying plant images in organized columns, ready for styling in the next lesson. Don't worry if it looks basic at first that's exactly what HTML should do before CSS adds the visual polish.
>
> [![Git and GitHub basics video](https://img.youtube.com/vi/1TvxJKBzhyQ/0.jpg)](https://www.youtube.com/watch?v=1TvxJKBzhyQ)
## Pre-Lecture Quiz
### Introduction
[Pre-lecture quiz](https://ff-quizzes.netlify.app/web/quiz/15)
HTML, or HyperText Markup Language, is the 'skeleton' of the web. If CSS 'dresses up' your HTML and JavaScript brings it to life, HTML is the body of your web application. HTML's syntax even reflects that idea, as it includes "head", "body", and "footer" tags.
> 📺 **Watch and Learn**: Check out this helpful video overview
>
> [![HTML Fundamentals Video](https://img.youtube.com/vi/1TvxJKBzhyQ/0.jpg)](https://www.youtube.com/watch?v=1TvxJKBzhyQ)
In this lesson, we're going to use HTML to layout the 'skeleton' of our virtual terrarium's interface. It will have a title and three columns: a right and a left column where the draggable plants live, and a center area that will be the actual glass-looking terrarium. By the end of this lesson, you will be able to see the plants in the columns, but the interface will look a little strange; don't worry, in the next section you will add CSS styles to the interface to make it look better.
## Setting Up Your Project
### Task
Before we dive into HTML code, let's set up a proper workspace for your terrarium project. Creating an organized file structure from the beginning is a crucial habit that will serve you well throughout your web development journey.
On your computer, create a folder called 'terrarium' and inside it, a file called 'index.html'. You can do this in Visual Studio Code after you create your terrarium folder by opening a new VS Code window, clicking 'open folder', and navigating to your new folder. Click the small 'file' button in the Explorer pane and create the new file:
### Task: Create Your Project Structure
![explorer in VS Code](images/vs-code-index.png)
You'll create a dedicated folder for your terrarium project and add your first HTML file. Here are two approaches you can use:
Or
**Option 1: Using Visual Studio Code**
1. Open Visual Studio Code
2. Click "File" → "Open Folder" or use `Ctrl+K, Ctrl+O` (Windows/Linux) or `Cmd+K, Cmd+O` (Mac)
3. Create a new folder called `terrarium` and select it
4. In the Explorer pane, click the "New File" icon
5. Name your file `index.html`
Use these commands on your git bash:
* `mkdir terrarium`
* `cd terrarium`
* `touch index.html`
* `code index.html` or `nano index.html`
![VS Code Explorer showing new file creation](images/vs-code-index.png)
> index.html files indicate to a browser that it is the default file in a folder; URLs such as `https://anysite.com/test` might be built using a folder structure including a folder called `test` with `index.html` inside it; `index.html` doesn't have to show in a URL.
**Option 2: Using Terminal Commands**
```bash
mkdir terrarium
cd terrarium
touch index.html
code index.html
```
---
**Here's what these commands accomplish:**
- **Creates** a new directory called `terrarium` for your project
- **Navigates** into the terrarium directory
- **Creates** an empty `index.html` file
- **Opens** the file in Visual Studio Code for editing
## The DocType and html tags
> 💡 **Pro Tip**: The filename `index.html` is special in web development. When someone visits a website, browsers automatically look for `index.html` as the default page to display. This means a URL like `https://mysite.com/projects/` will automatically serve the `index.html` file from the `projects` folder without needing to specify the filename in the URL.
The first line of an HTML file is its doctype. It's a little surprising that you need to have this line at the very top of the file, but it tells older browsers that the browser needs to render the page in a standard mode, following the current html specification.
## Understanding HTML Document Structure
> Tip: in VS Code, you can hover over a tag and get information about its use from the MDN Reference guides.
Every HTML document follows a specific structure that browsers need to understand and display correctly. Think of this structure like a formal letter it has required elements in a particular order that help the recipient (in this case, the browser) process the content properly.
The second line should be the `<html>` tag's opening tag, followed right now by its closing tag `</html>`. These tags are the root elements of your interface.
Let's start by adding the essential foundation that every HTML document needs.
### Task
### The DOCTYPE Declaration and Root Element
Add these lines at the top of your `index.html` file:
The first two lines of any HTML file serve as the document's "introduction" to the browser:
```HTML
```html
<!DOCTYPE html>
<html></html>
```
✅ There are a few different modes that can be determined by setting the DocType with a query string: [Quirks Mode and Standards Mode](https://developer.mozilla.org/docs/Web/HTML/Quirks_Mode_and_Standards_Mode). These modes used to support really old browsers that aren't normally used nowadays (Netscape Navigator 4 and Internet Explorer 5). You can stick to the standard doctype declaration.
**Understanding what this code does:**
- **Declares** the document type as HTML5 using `<!DOCTYPE html>`
- **Creates** the root `<html>` element that will contain all page content
- **Establishes** modern web standards for proper browser rendering
- **Ensures** consistent display across different browsers and devices
---
> 💡 **VS Code Tip**: Hover over any HTML tag in VS Code to see helpful information from MDN Web Docs, including usage examples and browser compatibility details.
## The document's 'head'
> 📚 **Learn More**: The DOCTYPE declaration prevents browsers from entering "quirks mode," which was used to support very old websites. Modern web development uses the simple `<!DOCTYPE html>` declaration to ensure [standards-compliant rendering](https://developer.mozilla.org/docs/Web/HTML/Quirks_Mode_and_Standards_Mode).
The 'head' area of the HTML document includes crucial information about your web page, also known as [metadata](https://developer.mozilla.org/docs/Web/HTML/Element/meta). In our case, we tell the web server to which this page will be sent to be rendered, these four things:
## Adding Essential Document Metadata
- the page's title
- page metadata including:
- the 'character set', telling about what character encoding is used in the page
- browser information, including `x-ua-compatible` which indicates that the IE=edge browser is supported
- information about how the viewport should behave when it is loaded. Setting the viewport to have an initial scale of 1 controls the zoom level when the page is first loaded.
The `<head>` section of an HTML document contains crucial information that browsers and search engines need, but that visitors don't see directly on the page. Think of it as the "behind-the-scenes" information that helps your webpage work properly and appear correctly across different devices and platforms.
### Task
This metadata tells browsers how to display your page, what character encoding to use, and how to handle different screen sizes all essential for creating professional, accessible web pages.
Add a 'head' block to your document in between the opening and closing `<html>` tags.
### Task: Add the Document Head
Insert this `<head>` section between your opening and closing `<html>` tags:
```html
<head>
@ -81,17 +94,28 @@ Add a 'head' block to your document in between the opening and closing `<html>`
</head>
```
✅ What would happen if you set a viewport meta tag like this: `<meta name="viewport" content="width=600">`? Read more about the [viewport](https://developer.mozilla.org/docs/Web/HTML/Viewport_meta_tag).
**Breaking down what each element accomplishes:**
- **Sets** the page title that appears in browser tabs and search results
- **Specifies** UTF-8 character encoding for proper text display worldwide
- **Ensures** compatibility with modern versions of Internet Explorer
- **Configures** responsive design by setting the viewport to match device width
- **Controls** initial zoom level to display content at natural size
---
> 🤔 **Think About This**: What would happen if you set a viewport meta tag like this: `<meta name="viewport" content="width=600">`? This would force the page to always be 600 pixels wide, breaking responsive design! Learn more about [proper viewport configuration](https://developer.mozilla.org/docs/Web/HTML/Viewport_meta_tag).
## The document's `body`
## Building the Document Body
### HTML Tags
The `<body>` element contains all the visible content of your webpage everything users will see and interact with. While the `<head>` section provided instructions to the browser, the `<body>` section contains the actual content: text, images, buttons, and other elements that create your user interface.
In HTML, you add tags to your .html file to create elements of a web page. Each tag usually has an opening and closing tag, like this: `<p>hello</p>` to indicate a paragraph. Create your interface's body by adding a set of `<body>` tags inside the `<html>` tag pair; your markup now looks like this:
Let's add the body structure and understand how HTML tags work together to create meaningful content.
### Task
### Understanding HTML Tag Structure
HTML uses paired tags to define elements. Most tags have an opening tag like `<p>` and a closing tag like `</p>`, with content in between: `<p>Hello, world!</p>`. This creates a paragraph element containing the text "Hello, world!".
### Task: Add the Body Element
Update your HTML file to include the `<body>` element:
```html
<!DOCTYPE html>
@ -106,17 +130,28 @@ In HTML, you add tags to your .html file to create elements of a web page. Each
</html>
```
Now, you can start building out your page. Normally, you use `<div>` tags to create the separate elements in a page. We'll create a series of `<div>` elements which will contain images.
**Here's what this complete structure provides:**
- **Establishes** the basic HTML5 document framework
- **Includes** essential metadata for proper browser rendering
- **Creates** an empty body ready for your visible content
- **Follows** modern web development best practices
Now you're ready to add the visible elements of your terrarium. We'll use `<div>` elements as containers to organize different sections of content, and `<img>` elements to display the plant images.
### Images
### Working with Images and Layout Containers
One html tag that doesn't need a closing tag is the `<img>` tag, because it has a `src` element that contains all the information the page needs to render the item.
Images are special in HTML because they use "self-closing" tags. Unlike elements like `<p></p>` that wrap around content, the `<img>` tag contains all the information it needs within the tag itself using attributes like `src` for the image file path and `alt` for accessibility.
Create a folder in your app called `images` and in that, add all the images in the [source code folder](../solution/images); (there are 14 images of plants).
Before adding images to your HTML, you'll need to organize your project files properly by creating an images folder and adding the plant graphics.
### Task
**First, set up your images:**
1. Create a folder called `images` inside your terrarium project folder
2. Download the plant images from the [solution folder](../solution/images) (14 plant images total)
3. Copy all plant images into your new `images` folder
Add those plant images into two columns between the `<body></body>` tags:
### Task: Create the Plant Display Layout
Now add the plant images organized in two columns between your `<body></body>` tags:
```html
<div id="page">
@ -169,37 +204,62 @@ Add those plant images into two columns between the `<body></body>` tags:
</div>
```
> Note: Spans vs. Divs. Divs are considered 'block' elements, and Spans are 'inline'. What would happen if you transformed these divs to spans?
**Step by step, here's what's happening in this code:**
- **Creates** a main page container with `id="page"` to hold all content
- **Establishes** two column containers: `left-container` and `right-container`
- **Organizes** 7 plants in the left column and 7 plants in the right column
- **Wraps** each plant image in a `plant-holder` div for individual positioning
- **Applies** consistent class names for CSS styling in the next lesson
- **Assigns** unique IDs to each plant image for JavaScript interaction later
- **Includes** proper file paths pointing to the images folder
With this markup, the plants now show up on the screen. It looks pretty bad, because they aren't yet styled using CSS, and we'll do that in the next lesson.
> 🤔 **Consider This**: Notice that all images currently have the same alt text "plant". This isn't ideal for accessibility. Screen reader users would hear "plant" repeated 14 times without knowing which specific plant each image shows. Can you think of better, more descriptive alt text for each image?
Each image has alt text that will appear even if you can't see or render an image. This is an important attribute to include for accessibility. Learn more about accessibility in future lessons; for now, remember that the alt attribute provides alternative information for an image if a user for some reason cannot view it (because of slow connection, an error in the src attribute, or if the user uses a screen reader).
> 📝 **HTML Element Types**: `<div>` elements are "block-level" and take up full width, while `<span>` elements are "inline" and only take up necessary width. What do you think would happen if you changed all these `<div>` tags to `<span>` tags?
✅ Did you notice that each image has the same alt tag? Is this good practice? Why or why not? Can you improve this code?
With this markup added, the plants will appear on screen, though they won't look polished yet that's what CSS is for in the next lesson! For now, you have a solid HTML foundation that properly organizes your content and follows accessibility best practices.
---
## Using Semantic HTML for Accessibility
Semantic HTML means choosing HTML elements based on their meaning and purpose, not just their appearance. When you use semantic markup, you're communicating the structure and meaning of your content to browsers, search engines, and assistive technologies like screen readers.
## Semantic markup
This approach makes your websites more accessible to users with disabilities and helps search engines better understand your content. It's a fundamental principle of modern web development that creates better experiences for everyone.
In general, it's preferable to use meaningful 'semantics' when writing HTML. What does that mean? It means that you use HTML tags to represent the type of data or interaction they were designed for. For example, the main title text on a page should use an `<h1>` tag.
### Adding a Semantic Page Title
Add the following line right below your opening `<body>` tag:
Let's add a proper heading to your terrarium page. Insert this line right after your opening `<body>` tag:
```html
<h1>My Terrarium</h1>
```
Using semantic markup such as having headers be `<h1>` and unordered lists be rendered as `<ul>` helps screen readers navigate through a page. In general, buttons should be written as `<button>` and lists should be `<li>`. While it's _possible_ to use specially styled `<span>` elements with click handlers to mock buttons, it's better for disabled users to use technologies to determine where on a page a button resides, and to interact with it, if the element appears as a button. For this reason, try to use semantic markup as much as possible.
**Why semantic markup matters:**
- **Helps** screen readers navigate and understand page structure
- **Improves** search engine optimization (SEO) by clarifying content hierarchy
- **Enhances** accessibility for users with visual impairments or cognitive differences
- **Creates** better user experiences across all devices and platforms
- **Follows** web standards and best practices for professional development
**Examples of semantic vs. non-semantic choices:**
| Purpose | ✅ Semantic Choice | ❌ Non-Semantic Choice |
|---------|-------------------|------------------------|
| Main heading | `<h1>Title</h1>` | `<div class="big-text">Title</div>` |
| Navigation | `<nav><ul><li></li></ul></nav>` | `<div class="menu"><div></div></div>` |
| Button | `<button>Click me</button>` | `<span onclick="...">Click me</span>` |
| Article content | `<article><p></p></article>` | `<div class="content"><div></div></div>` |
✅ Take a look at a screen reader and [how it interacts with a web page](https://www.youtube.com/watch?v=OUDV1gqs9GA). Can you see why having non semantic markup might frustrate the user?
> 🎥 **See It in Action**: Watch [how screen readers interact with web pages](https://www.youtube.com/watch?v=OUDV1gqs9GA) to understand why semantic markup is crucial for accessibility. Notice how proper HTML structure helps users navigate efficiently.
## The terrarium
## Creating the Terrarium Container
The last part of this interface involves creating markup that will be styled to create a terrarium.
Now let's add the HTML structure for the terrarium itself the glass container where plants will eventually be placed. This section demonstrates an important concept: HTML provides structure, but without CSS styling, these elements won't be visible yet.
### Task:
The terrarium markup uses descriptive class names that will make CSS styling intuitive and maintainable in the next lesson.
Add this markup above the last `</div>` tag:
### Task: Add the Terrarium Structure
Insert this markup above the last `</div>` tag (before the closing tag of the page container):
```html
<div id="terrarium">
@ -213,11 +273,18 @@ Add this markup above the last `</div>` tag:
</div>
```
✅ Even though you added this markup to the screen, you see absolutely nothing render. Why?
**Understanding this terrarium structure:**
- **Creates** a main terrarium container with a unique ID for styling
- **Defines** separate elements for each visual component (top, walls, dirt, bottom)
- **Includes** nested elements for glass reflection effects (glossy elements)
- **Uses** descriptive class names that clearly indicate each element's purpose
- **Prepares** the structure for CSS styling that will create the glass terrarium appearance
> 🤔 **Notice Something?**: Even though you added this markup, you don't see anything new on the page! This perfectly illustrates how HTML provides structure while CSS provides appearance. These `<div>` elements exist but have no visual styling yet that's coming in the next lesson!
---
## GitHub Copilot Agent Challenge 🚀
## GitHub Copilot Agent Challenge
Use the Agent mode to complete the following challenge:
@ -225,9 +292,26 @@ Use the Agent mode to complete the following challenge:
**Prompt:** Create a semantic HTML section that includes a main heading "Plant Care Guide", three subsections with headings "Watering", "Light Requirements", and "Soil Care", each containing a paragraph of plant care information. Use proper semantic HTML tags like `<section>`, `<h2>`, `<h3>`, and `<p>` to structure the content appropriately.
## 🚀Challenge
Learn more about [agent mode](https://code.visualstudio.com/blogs/2025/02/24/introducing-copilot-agent-mode) here.
## Explore HTML History Challenge
**Learning About Web Evolution**
There are some wild 'older' tags in HTML that are still fun to play with, though you shouldn't use deprecated tags such as [these tags](https://developer.mozilla.org/docs/Web/HTML/Element#Obsolete_and_deprecated_elements) in your markup. Still, can you use the old `<marquee>` tag to make the h1 title scroll horizontally? (if you do, don't forget to remove it afterwards)
HTML has evolved significantly since Tim Berners-Lee created the first web browser at CERN in 1990. Some older tags like `<marquee>` are now deprecated because they don't work well with modern accessibility standards and responsive design principles.
**Try This Experiment:**
1. Temporarily wrap your `<h1>` title in a `<marquee>` tag: `<marquee><h1>My Terrarium</h1></marquee>`
2. Open your page in a browser and observe the scrolling effect
3. Consider why this tag was deprecated (hint: think about user experience and accessibility)
4. Remove the `<marquee>` tag and return to semantic markup
**Reflection Questions:**
- How might a scrolling title affect users with visual impairments or motion sensitivity?
- What modern CSS techniques could achieve similar visual effects more accessibly?
- Why is it important to use current web standards instead of deprecated elements?
Explore more about [obsolete and deprecated HTML elements](https://developer.mozilla.org/docs/Web/HTML/Element#Obsolete_and_deprecated_elements) to understand how web standards evolve to improve user experience.
## Post-Lecture Quiz
@ -236,9 +320,31 @@ There are some wild 'older' tags in HTML that are still fun to play with, though
## Review & Self Study
HTML is the 'tried and true' building block system that has helped build the web into what it is today. Learn a little about its history by studying some old and new tags. Can you figure out why some tags were deprecated and some added? What tags might be introduced in the future?
**Deepen Your HTML Knowledge**
HTML has been the foundation of the web for over 30 years, evolving from a simple document markup language to a sophisticated platform for building interactive applications. Understanding this evolution helps you appreciate modern web standards and make better development decisions.
**Recommended Learning Paths:**
1. **HTML History and Evolution**
- Research the timeline from HTML 1.0 to HTML5
- Explore why certain tags were deprecated (accessibility, mobile-friendliness, maintainability)
- Investigate emerging HTML features and proposals
2. **Semantic HTML Deep Dive**
- Study the complete list of [HTML5 semantic elements](https://developer.mozilla.org/docs/Web/HTML/Element)
- Practice identifying when to use `<article>`, `<section>`, `<aside>`, and `<main>`
- Learn about ARIA attributes for enhanced accessibility
3. **Modern Web Development**
- Explore [building responsive websites](https://docs.microsoft.com/learn/modules/build-simple-website/?WT.mc_id=academic-77807-sagibbon) on Microsoft Learn
- Understand how HTML integrates with CSS and JavaScript
- Learn about web performance and SEO best practices
Learn more about building sites for the web and mobile devices at [Microsoft Learn](https://docs.microsoft.com/learn/modules/build-simple-website/?WT.mc_id=academic-77807-sagibbon).
**Reflection Questions:**
- Which deprecated HTML tags did you discover, and why were they removed?
- What new HTML features are being proposed for future versions?
- How does semantic HTML contribute to web accessibility and SEO?
## Assignment

@ -1,39 +1,138 @@
# HTML Practice Assignment: Build a Blog Mockup
## Objective
## Learning Objectives
Design and hand-code the HTML structure for a personal blog homepage. This exercise will help you practice semantic HTML, layout planning, and code organization.
Apply your HTML knowledge by designing and coding a complete blog homepage structure. This hands-on assignment will reinforce semantic HTML concepts, accessibility best practices, and professional code organization skills that you'll use throughout your web development journey.
## Instructions
**By completing this assignment, you will:**
- Practice planning website layouts before coding
- Apply semantic HTML elements appropriately
- Create accessible, well-structured markup
- Develop professional coding habits with comments and organization
1. **Design Your Blog Mockup**
- Sketch a visual mockup of your blog homepage. Include key sections such as header, navigation, main content, sidebar, and footer.
- You may use paper and scan your sketch, or use digital tools (e.g., Figma, Adobe XD, Canva, or even PowerPoint).
## Project Requirements
2. **Identify HTML Elements**
- List the HTML elements you plan to use for each section (e.g., `<header>`, `<nav>`, `<main>`, `<article>`, `<aside>`, `<footer>`, `<section>`, `<h1>``<h6>`, `<p>`, `<img>`, `<ul>`, `<li>`, `<a>`, etc.).
### Part 1: Design Planning (Visual Mockup)
3. **Write the HTML Markup**
- Hand-code the HTML for your mockup. Focus on semantic structure and best practices.
- Include at least 10 distinct HTML elements.
- Add comments to explain your choices and structure.
**Create a visual mockup of your blog homepage that includes:**
- Header with site title and navigation
- Main content area with at least 2-3 blog post previews
- Sidebar with additional information (about section, recent posts, categories)
- Footer with contact information or links
4. **Submit Your Work**
- Upload your sketch/mockup and your HTML file.
- Optionally, provide a brief reflection (23 sentences) on your design decisions.
**Mockup Creation Options:**
- **Hand-drawn sketch**: Use paper and pencil, then photograph or scan your design
- **Digital tools**: Figma, Adobe XD, Canva, PowerPoint, or any drawing application
- **Wireframe tools**: Balsamiq, MockFlow, or similar wireframing software
## Rubric
**Label your mockup sections** with the HTML elements you plan to use (e.g., "Header - `<header>`", "Blog Posts - `<article>`").
| Criteria | Exemplary | Adequate | Needs Improvement |
|------------------|--------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------|---------------------------------------------------------------------------------|
| Visual Mockup | Clear, detailed mockup with labeled sections and thoughtful layout | Basic mockup with some labeled sections | Minimal or unclear mockup; lacks section labels |
| HTML Elements | Uses 10+ semantic HTML elements; demonstrates understanding of structure and best practices | Uses 59 HTML elements; some semantic structure | Uses fewer than 5 elements; lacks semantic structure |
| Code Quality | Well-organized, readable code with comments; follows HTML standards | Mostly organized code; few comments | Disorganized code; lacks comments |
| Reflection | Insightful reflection on design choices and challenges | Basic reflection | No reflection or lacks relevance |
### Part 2: HTML Element Planning
## Tips
**Create a list mapping each section of your mockup to specific HTML elements:**
- Use semantic HTML tags for better accessibility and SEO.
- Organize your code with indentation and comments.
- Refer to [MDN HTML Elements Reference](https://developer.mozilla.org/en-US/docs/Web/HTML/Element) for guidance.
- Think about how your layout could be extended or styled in future assignments.
```
Example:
- Site Header → <header>
- Main Navigation → <nav> with <ul> and <li>
- Blog Post → <article> with <h2>, <p>, <time>
- Sidebar → <aside> with <section> elements
- Page Footer → <footer>
```
**Required Elements to Include:**
Your HTML must contain at least 10 different semantic elements from this list:
- `<header>`, `<nav>`, `<main>`, `<article>`, `<section>`, `<aside>`, `<footer>`
- `<h1>`, `<h2>`, `<h3>`, `<p>`, `<ul>`, `<li>`, `<a>`
- `<img>`, `<time>`, `<blockquote>`, `<strong>`, `<em>`
### Part 3: HTML Implementation
**Code your blog homepage following these standards:**
1. **Document Structure**: Include proper DOCTYPE, html, head, and body elements
2. **Semantic Markup**: Use HTML elements for their intended purpose
3. **Accessibility**: Include proper alt text for images and meaningful link text
4. **Code Quality**: Use consistent indentation and meaningful comments
5. **Content**: Include realistic blog content (you can use placeholder text)
**Sample HTML Structure:**
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Personal Blog</title>
</head>
<body>
<!-- Main site header -->
<header>
<h1>My Blog Title</h1>
<nav>
<!-- Navigation menu -->
</nav>
</header>
<!-- Main content area -->
<main>
<!-- Blog posts go here -->
</main>
<!-- Sidebar content -->
<aside>
<!-- Additional information -->
</aside>
<!-- Site footer -->
<footer>
<!-- Footer content -->
</footer>
</body>
</html>
```
### Part 4: Reflection
**Write a brief reflection (3-5 sentences) addressing:**
- Which HTML elements were you most confident using?
- What challenges did you encounter while planning or coding?
- How did semantic HTML help organize your content?
- What would you do differently in your next HTML project?
## Submission Checklist
**Before submitting, ensure you have:**
- [ ] Visual mockup with labeled HTML elements
- [ ] Complete HTML file with proper document structure
- [ ] At least 10 different semantic HTML elements used appropriately
- [ ] Meaningful comments explaining your code structure
- [ ] Valid HTML syntax (test in a browser)
- [ ] Written reflection addressing the prompt questions
## Assessment Rubric
| Criteria | Exemplary (4) | Proficient (3) | Developing (2) | Beginning (1) |
|----------|---------------|----------------|----------------|---------------|
| **Planning & Design** | Detailed, well-labeled mockup showing clear understanding of layout and HTML semantic structure | Clear mockup with most sections labeled appropriately | Basic mockup with some labeling, shows general understanding | Minimal or unclear mockup, lacks proper section identification |
| **Semantic HTML Usage** | Uses 10+ semantic elements appropriately, demonstrates deep understanding of HTML structure and accessibility | Uses 8-9 semantic elements correctly, shows good understanding of semantic markup | Uses 6-7 semantic elements, some confusion about appropriate usage | Uses fewer than 6 elements or misuses semantic elements |
| **Code Quality & Organization** | Exceptionally well-organized, properly indented code with comprehensive comments and perfect HTML syntax | Well-organized code with good indentation, helpful comments, and valid syntax | Mostly organized code with some comments, minor syntax issues | Poor organization, minimal comments, multiple syntax errors |
| **Accessibility & Best Practices** | Excellent accessibility considerations, meaningful alt text, proper heading hierarchy, follows all modern HTML best practices | Good accessibility features, appropriate use of headings and alt text, follows most best practices | Some accessibility considerations, basic alt text and heading structure | Limited accessibility features, poor heading structure, doesn't follow best practices |
| **Reflection & Learning** | Insightful reflection demonstrating deep understanding of HTML concepts and thoughtful analysis of the learning process | Good reflection showing understanding of key concepts and some self-awareness of learning | Basic reflection with limited insight into HTML concepts or learning process | Minimal or missing reflection, shows little understanding of concepts learned |
## Learning Resources
**Essential References:**
- [MDN HTML Elements Reference](https://developer.mozilla.org/docs/Web/HTML/Element) - Complete guide to all HTML elements
- [HTML5 Semantic Elements](https://developer.mozilla.org/docs/Web/HTML/Element#content_sectioning) - Understanding semantic markup
- [Web Accessibility Guidelines](https://www.w3.org/WAI/WCAG21/quickref/) - Creating accessible web content
- [HTML Validator](https://validator.w3.org/) - Check your HTML syntax
**Pro Tips for Success:**
- Start with your mockup before writing any code
- Use the browser's developer tools to inspect your HTML structure
- Test your page with different screen sizes (even without CSS)
- Read your HTML aloud to check if the structure makes logical sense
- Consider how a screen reader would interpret your page structure
> 💡 **Remember**: This assignment focuses on HTML structure and semantics. Don't worry about visual styling that's what CSS is for! Your page might look plain, but it should be well-structured and meaningful.

@ -3,249 +3,418 @@
![Introduction to CSS](../../sketchnotes/webdev101-css.png)
> Sketchnote by [Tomomi Imura](https://twitter.com/girlie_mac)
Remember how your HTML terrarium looked quite basic? CSS is where we transform that plain structure into something visually appealing.
If HTML is like building the frame of a house, then CSS is everything that makes it feel like home - the paint colors, the furniture arrangement, the lighting, and how the rooms flow together. Think of how the Palace of Versailles started as a simple hunting lodge, but careful attention to decoration and layout transformed it into one of the world's most magnificent buildings.
Today, we'll transform your terrarium from functional to polished. You'll learn how to position elements precisely, make layouts respond to different screen sizes, and create the visual appeal that makes websites engaging.
By the end of this lesson, you'll see how strategic CSS styling can dramatically improve your project. Let's add some style to your terrarium.
## Pre-Lecture Quiz
[Pre-lecture quiz](https://ff-quizzes.netlify.app/web/quiz/17)
### Introduction
## Getting Started with CSS
CSS, or Cascading Style Sheets, solve an important problem of web development: how to make your web site look nice. Styling your apps makes them more usable and nicer-looking; you can also use CSS to create Responsive Web Design (RWD) - allowing your apps to look good no matter what screen size they are displayed on. CSS is not only about making your app look nice; its spec includes animations and transforms that can enable sophisticated interactions for your apps. The CSS Working Group helps maintain current CSS specifications; you can follow their work at [World Wide Web Consortium's site](https://www.w3.org/Style/CSS/members).
CSS is often thought of as just "making things pretty," but it serves a much broader purpose. CSS is like being the director of a movie - you control not just how everything looks, but how it moves, responds to interaction, and adapts to different situations.
> Note, CSS is a language that evolves, like everything on the web, and not all browsers support newer parts of the specification. Always check your implementations by consulting [CanIUse.com](https://caniuse.com).
Modern CSS is remarkably capable. You can write code that automatically adjusts layouts for phones, tablets, and desktop computers. You can create smooth animations that guide users' attention where needed. The results can be quite impressive when everything works together.
In this lesson, we're going to add styles to our online terrarium and learn more about several CSS concepts: the cascade, inheritance, and the use of selectors, positioning, and using CSS to build layouts. In the process we will layout the terrarium and create the actual terrarium itself.
> 💡 **Pro Tip**: CSS is constantly evolving with new features and capabilities. Always check [CanIUse.com](https://caniuse.com) to verify browser support for newer CSS features before using them in production projects.
**Here's what we'll accomplish in this lesson:**
- **Creates** a complete visual design for your terrarium using modern CSS techniques
- **Explores** fundamental concepts like the cascade, inheritance, and CSS selectors
- **Implements** responsive positioning and layout strategies
- **Builds** the terrarium container using CSS shapes and styling
### Prerequisite
You should have the HTML for your terrarium built and ready to be styled.
You should have completed the HTML structure for your terrarium from the previous lesson and have it ready to be styled.
> Check out video
> 📺 **Video Resource**: Check out this helpful video walkthrough
>
> [![CSS Basics Tutorial](https://img.youtube.com/vi/6yIdOIV9p1I/0.jpg)](https://www.youtube.com/watch?v=6yIdOIV9p1I)
>
> [![Git and GitHub basics video](https://img.youtube.com/vi/6yIdOIV9p1I/0.jpg)](https://www.youtube.com/watch?v=6yIdOIV9p1I)
### Setting Up Your CSS File
### Task
Before we can start styling, we need to connect CSS to our HTML. This connection tells the browser where to find the styling instructions for our terrarium.
In your terrarium folder, create a new file called `style.css`. Import that file in the `<head>` section:
In your terrarium folder, create a new file called `style.css`, then link it in your HTML document's `<head>` section:
```html
<link rel="stylesheet" href="./style.css" />
```
---
**Here's what this code does:**
- **Creates** a connection between your HTML and CSS files
- **Tells** the browser to load and apply the styles from `style.css`
- **Uses** the `rel="stylesheet"` attribute to specify this is a CSS file
- **References** the file path with `href="./style.css"`
## Understanding the CSS Cascade
## The Cascade
Ever wondered why CSS is called "Cascading" Style Sheets? Styles cascade down like a waterfall, and sometimes they conflict with each other.
Cascading Style Sheets incorporate the idea that the styles 'cascade' such that the application of a style is guided by its priority. Styles set by a web site author take priority over those set by a browser. Styles set 'inline' take priority over those set in an external style sheet.
Consider how military command structures work - a general order might say "all troops wear green," but a specific order to your unit might say "wear dress blues for the ceremony." The more specific instruction takes precedence. CSS follows similar logic, and understanding this hierarchy makes debugging much more manageable.
### Task
### Experimenting with Cascade Priority
Add the inline style "color: red" to your `<h1>` tag:
Let's see the cascade in action by creating a style conflict. First, add an inline style to your `<h1>` tag:
```HTML
```html
<h1 style="color: red">My Terrarium</h1>
```
Then, add the following code to your `style.css` file:
**What this code does:**
- **Applies** a red color directly to the `<h1>` element using inline styling
- **Uses** the `style` attribute to embed CSS directly in the HTML
- **Creates** the highest priority style rule for this specific element
Next, add this rule to your `style.css` file:
```CSS
```css
h1 {
color: blue;
color: blue;
}
```
✅ Which color displays in your web app? Why? Can you find a way to override styles? When would you want to do this, or why not?
**In the above, we've:**
- **Defined** a CSS rule that targets all `<h1>` elements
- **Set** the text color to blue using an external stylesheet
- **Created** a lower priority rule compared to inline styles
---
**Knowledge Check**: Which color displays in your web app? Why does that color win? Can you think of scenarios where you might want to override styles?
## Inheritance
> 💡 **CSS Priority Order (highest to lowest):**
> 1. **Inline styles** (style attribute)
> 2. **IDs** (#myId)
> 3. **Classes** (.myClass) and attributes
> 4. **Element selectors** (h1, div, p)
> 5. **Browser defaults**
Styles are inherited from an ancestor style to a descendent, such that nested elements inherit the styles of their parents.
## CSS Inheritance in Action
### Task
CSS inheritance works like genetics - elements inherit certain properties from their parent elements. If you set the font family on the body element, all text inside automatically uses that same font. It's similar to how the Habsburg family's distinctive jawline appeared across generations without being specified for each individual.
Set the body's font to a given font, and check to see a nested element's font:
However, not everything gets inherited. Text styles like fonts and colors do inherit, but layout properties like margins and borders do not. Just as children might inherit physical traits but not their parents' fashion choices.
```CSS
### Observing Font Inheritance
Let's see inheritance in action by setting a font family on the `<body>` element:
```css
body {
font-family: helvetica, arial, sans-serif;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
```
Open your browser's console to the 'Elements' tab and observe the H1's font. It inherits its font from the body, as stated within the browser:
**Breaking down what happens here:**
- **Sets** the font family for the entire page by targeting the `<body>` element
- **Uses** a font stack with fallback options for better browser compatibility
- **Applies** modern system fonts that look great across different operating systems
- **Ensures** all child elements inherit this font unless specifically overridden
Open your browser's developer tools (F12), navigate to the Elements tab, and inspect your `<h1>` element. You'll see that it inherits the font family from the body:
![inherited font](images/1.png)
✅ Can you make a nested style inherit a different property?
**Experiment Time**: Try setting other inheritable properties on the `<body>` like `color`, `line-height`, or `text-align`. What happens to your heading and other elements?
---
> 📝 **Inheritable Properties Include**: `color`, `font-family`, `font-size`, `line-height`, `text-align`, `visibility`
>
> **Non-Inheritable Properties Include**: `margin`, `padding`, `border`, `width`, `height`, `position`
## Mastering CSS Selectors
## CSS Selectors
CSS selectors are your way of targeting specific elements for styling. They work like giving precise directions - instead of saying "the house," you might say "the blue house with the red door on Maple Street."
### Tags
CSS provides different ways to be specific, and choosing the right selector is like choosing the appropriate tool for the task. Sometimes you need to style every door in the neighborhood, and sometimes just one specific door.
So far, your `style.css` file has only a few tags styled, and the app looks pretty strange:
### Element Selectors (Tags)
```CSS
Element selectors target HTML elements by their tag name. They're perfect for setting base styles that apply broadly across your page:
```css
body {
font-family: helvetica, arial, sans-serif;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 0;
}
h1 {
color: #3a241d;
text-align: center;
color: #3a241d;
text-align: center;
font-size: 2.5rem;
margin-bottom: 1rem;
}
```
This way of styling a tag gives you control over unique elements, but you need to control the styles of many plants in your terrarium. To do that, you need to leverage CSS selectors.
**Understanding these styles:**
- **Sets** consistent typography across the entire page with the `body` selector
- **Removes** default browser margins and padding for better control
- **Styles** all heading elements with color, alignment, and spacing
- **Uses** `rem` units for scalable, accessible font sizing
While element selectors work well for general styling, you'll need more specific selectors to style individual components like the plants in your terrarium.
### Ids
### ID Selectors for Unique Elements
Add some style to layout the left and right containers. Since there is only one left container and only one right container, they are given ids in the markup. To style them, use `#`:
ID selectors use the `#` symbol and target elements with specific `id` attributes. Since IDs must be unique on a page, they're perfect for styling individual, special elements like our left and right plant containers.
```CSS
Let's create the styling for our terrarium's side containers where the plants will live:
```css
#left-container {
background-color: #eee;
width: 15%;
left: 0px;
top: 0px;
position: absolute;
height: 100%;
padding: 10px;
background-color: #f5f5f5;
width: 15%;
left: 0;
top: 0;
position: absolute;
height: 100vh;
padding: 1rem;
box-sizing: border-box;
}
#right-container {
background-color: #eee;
width: 15%;
right: 0px;
top: 0px;
position: absolute;
height: 100%;
padding: 10px;
background-color: #f5f5f5;
width: 15%;
right: 0;
top: 0;
position: absolute;
height: 100vh;
padding: 1rem;
box-sizing: border-box;
}
```
Here, you have placed these containers with absolute positioning to the far left and right of the screen, and used percentages for their width so that they can scale for small mobile screens.
**Here's what this code accomplishes:**
- **Positions** containers at the far left and right edges using `absolute` positioning
- **Uses** `vh` (viewport height) units for responsive height that adapts to screen size
- **Applies** `box-sizing: border-box` so padding is included in the total width
- **Removes** unnecessary `px` units from zero values for cleaner code
- **Sets** a subtle background color that's easier on the eyes than stark gray
✅ This code is quite repeated, thus not "DRY" (Don't Repeat Yourself); can you find a better way to style these ids, perhaps with an id and a class? You would need to change the markup and refactor the CSS:
**Code Quality Challenge**: Notice how this CSS violates the DRY (Don't Repeat Yourself) principle. Can you refactor it using both an ID and a class?
**Improved approach:**
```html
<div id="left-container" class="container"></div>
<div id="right-container" class="container"></div>
```
```css
.container {
background-color: #f5f5f5;
width: 15%;
top: 0;
position: absolute;
height: 100vh;
padding: 1rem;
box-sizing: border-box;
}
#left-container {
left: 0;
}
#right-container {
right: 0;
}
```
### Classes
### Class Selectors for Reusable Styles
In the example above, you styled two unique elements on the screen. If you want styles to apply to many elements on the screen, you can use CSS classes. Do this to layout the plants in the left and right containers.
Class selectors use the `.` symbol and are perfect when you want to apply the same styles to multiple elements. Unlike IDs, classes can be reused throughout your HTML, making them ideal for consistent styling patterns.
Notice that each plant in the HTML markup has a combination of ids and classes. The ids here are used by the JavaScript that you will add later to manipulate the terrarium plant placement. The classes, however, give all the plants a given style.
In our terrarium, each plant needs similar styling but also needs individual positioning. We'll use a combination of classes for shared styles and IDs for unique positioning.
**Here's the HTML structure for each plant:**
```html
<div class="plant-holder">
<img class="plant" alt="plant" id="plant1" src="./images/plant1.png" />
<img class="plant" alt="Decorative plant for terrarium" id="plant1" src="./images/plant1.png" />
</div>
```
Add the following to your `style.css` file:
**Key elements explained:**
- **Uses** `class="plant-holder"` for consistent container styling across all plants
- **Applies** `class="plant"` for shared image styling and behavior
- **Includes** unique `id="plant1"` for individual positioning and JavaScript interaction
- **Provides** descriptive alt text for screen reader accessibility
Now add these styles to your `style.css` file:
```CSS
```css
.plant-holder {
position: relative;
height: 13%;
left: -10px;
position: relative;
height: 13%;
left: -0.6rem;
}
.plant {
position: absolute;
max-width: 150%;
max-height: 150%;
z-index: 2;
position: absolute;
max-width: 150%;
max-height: 150%;
z-index: 2;
transition: transform 0.3s ease;
}
.plant:hover {
transform: scale(1.05);
}
```
Notable in this snippet is the mixture of relative and absolute positioning, which we'll cover in the next section. Take a look at the way heights are handled by percentages:
**Breaking down these styles:**
- **Creates** relative positioning for the plant holder to establish a positioning context
- **Sets** each plant holder to 13% height, ensuring all plants fit vertically without scrolling
- **Shifts** holders slightly left to better center plants within their containers
- **Allows** plants to scale responsively with `max-width` and `max-height` properties
- **Uses** `z-index` to layer plants above other elements in the terrarium
- **Adds** a subtle hover effect with CSS transitions for better user interaction
**Critical Thinking**: Why do we need both `.plant-holder` and `.plant` selectors? What would happen if we tried to use just one?
> 💡 **Design Pattern**: The container (`.plant-holder`) controls layout and positioning, while the content (`.plant`) controls appearance and scaling. This separation makes the code more maintainable and flexible.
## Understanding CSS Positioning
You set the height of the plant holder to 13%, a good number to ensure that all the plants are displayed in each vertical container without need for scrolling.
CSS positioning is like being the stage director for a play - you direct where every actor stands and how they move around the stage. Some actors follow the standard formation, while others need specific positioning for dramatic effect.
You set the plant holder to move to the left to allow the plants to be more centered within their container. The images have a large amount of transparent background so as to make them more draggable, so need to be pushed to the left to fit better on the screen.
Once you understand positioning, many layout challenges become manageable. Need a navigation bar that stays at the top while users scroll? Positioning handles that. Want a tooltip that appears at a specific location? That's positioning too.
Then, the plant itself is given a max-width of 150%. This allows it to scale down as the browser scales down. Try resizing your browser; the plants stay in their containers but scale down to fit.
### The Five Position Values
Also notable is the use of z-index, which controls the relative altitude of an element (so that the plants sit on top of the container and appear to sit inside the terrarium).
| Position Value | Behavior | Use Case |
|----------------|----------|----------|
| `static` | Default flow, ignores top/left/right/bottom | Normal document layout |
| `relative` | Positioned relative to its normal position | Small adjustments, creating positioning context |
| `absolute` | Positioned relative to nearest positioned ancestor | Precise placement, overlays |
| `fixed` | Positioned relative to viewport | Navigation bars, floating elements |
| `sticky` | Switches between relative and fixed based on scroll | Headers that stick when scrolling |
✅ Why do you need both a plant holder and a plant CSS selector?
### Positioning in Our Terrarium
## CSS Positioning
Our terrarium uses a strategic combination of positioning types to create the desired layout:
Mixing position properties (there are static, relative, fixed, absolute, and sticky positions) can be a little tricky, but when done properly it gives you good control over the elements on your pages.
```css
/* Container positioning */
.container {
position: absolute; /* Removes from normal flow */
/* ... other styles ... */
}
/* Plant holder positioning */
.plant-holder {
position: relative; /* Creates positioning context */
/* ... other styles ... */
}
/* Plant positioning */
.plant {
position: absolute; /* Allows precise placement within holder */
/* ... other styles ... */
}
```
Absolute positioned elements are positioned relative to their nearest positioned ancestors, and if there are none, it is positioned according to the document body.
**Understanding the positioning strategy:**
- **Absolute containers** are removed from normal document flow and pinned to screen edges
- **Relative plant holders** create a positioning context while staying in document flow
- **Absolute plants** can be positioned precisely within their relative containers
- **This combination** allows plants to stack vertically while being individually positionable
Relative positioned elements are positioned based on the CSS's directions to adjust its placement away from its initial position.
> 🎯 **Why This Matters**: The `plant` elements need absolute positioning to become draggable in the next lesson. Absolute positioning removes them from the normal layout flow, making drag-and-drop interactions possible.
In our sample, the `plant-holder` is a relative-positioned element that is positioned within an absolute-positioned container. The resultant behavior is that the side bar containers are pinned left and right, and the plant-holder is nested, adjusting itself within the side bars, giving space for the plants to be placed in a vertical row.
**Experiment Time**: Try changing the positioning values and observe the results:
- What happens if you change `.container` from `absolute` to `relative`?
- How does the layout change if `.plant-holder` uses `absolute` instead of `relative`?
- What occurs when you switch `.plant` to `relative` positioning?
> The `plant` itself also has absolute positioning, necessary to making it draggable, as you will discover in the next lesson.
## Building the Terrarium with CSS
✅ Experiment with switching the types of positioning of the side containers and the plant-holder. What happens?
Now we'll build a glass jar using only CSS - no images or graphics software required.
## CSS Layouts
Creating realistic-looking glass, shadows, and depth effects using positioning and transparency demonstrates CSS's visual capabilities. This technique mirrors how architects in the Bauhaus movement used simple geometric forms to create complex, beautiful structures. Once you understand these principles, you'll recognize the CSS techniques behind many web designs.
Now you will use what you learned to build the terrarium itself, all using CSS!
### Creating the Glass Jar Components
First, style the `.terrarium` div children as a rounded rectangle using CSS:
Let's build the terrarium jar piece by piece. Each part uses absolute positioning and percentage-based sizing for responsive design:
```CSS
```css
.jar-walls {
height: 80%;
width: 60%;
background: #d1e1df;
border-radius: 1rem;
position: absolute;
bottom: 0.5%;
left: 20%;
opacity: 0.5;
z-index: 1;
height: 80%;
width: 60%;
background: #d1e1df;
border-radius: 1rem;
position: absolute;
bottom: 0.5%;
left: 20%;
opacity: 0.5;
z-index: 1;
box-shadow: inset 0 0 2rem rgba(0, 0, 0, 0.1);
}
.jar-top {
width: 50%;
height: 5%;
background: #d1e1df;
position: absolute;
bottom: 80.5%;
left: 25%;
opacity: 0.7;
z-index: 1;
width: 50%;
height: 5%;
background: #d1e1df;
position: absolute;
bottom: 80.5%;
left: 25%;
opacity: 0.7;
z-index: 1;
border-radius: 0.5rem 0.5rem 0 0;
}
.jar-bottom {
width: 50%;
height: 1%;
background: #d1e1df;
position: absolute;
bottom: 0%;
left: 25%;
opacity: 0.7;
width: 50%;
height: 1%;
background: #d1e1df;
position: absolute;
bottom: 0;
left: 25%;
opacity: 0.7;
border-radius: 0 0 0.5rem 0.5rem;
}
.dirt {
width: 60%;
height: 5%;
background: #3a241d;
position: absolute;
border-radius: 0 0 1rem 1rem;
bottom: 1%;
left: 20%;
opacity: 0.7;
z-index: -1;
width: 60%;
height: 5%;
background: #3a241d;
position: absolute;
border-radius: 0 0 1rem 1rem;
bottom: 1%;
left: 20%;
opacity: 0.7;
z-index: -1;
}
```
Note the use of percentages here. If you scale your browser down, you can see how the jar scales as well. Also notice the widths and height percentages for the jar elements and how each element is absolutely positioned in the center, pinned to the bottom of the viewport.
**Understanding the terrarium construction:**
- **Uses** percentage-based dimensions for responsive scaling across all screen sizes
- **Positions** elements absolutely to stack and align them precisely
- **Applies** different opacity values to create the glass transparency effect
- **Implements** `z-index` layering so plants appear inside the jar
- **Adds** subtle box-shadow and refined border-radius for more realistic appearance
### Responsive Design with Percentages
Notice how all dimensions use percentages rather than fixed pixel values:
We are also using `rem` for the border-radius, a font-relative length. Read more about this type of relative measurement in the [CSS spec](https://www.w3.org/TR/css-values-3/#font-relative-lengths).
**Why this matters:**
- **Ensures** the terrarium scales proportionally on any screen size
- **Maintains** the visual relationships between jar components
- **Provides** a consistent experience from mobile phones to large desktop monitors
- **Allows** the design to adapt without breaking the visual layout
✅ Try changing the jar colors and opacity vs. those of the dirt. What happens? Why?
### CSS Units in Action
We're using `rem` units for border-radius, which scale relative to the root font size. This creates more accessible designs that respect user font preferences. Learn more about [CSS relative units](https://www.w3.org/TR/css-values-3/#font-relative-lengths) in the official specification.
**Visual Experimentation**: Try modifying these values and observe the effects:
- Change the jar opacity from 0.5 to 0.8 how does this affect the glass appearance?
- Adjust the dirt color from `#3a241d` to `#8B4513` what visual impact does this have?
- Modify the `z-index` of the dirt to 2 what happens to the layering?
---
@ -257,25 +426,47 @@ Use the Agent mode to complete the following challenge:
**Prompt:** Add CSS keyframe animations to make the plants in the terrarium sway gently from side to side. Create a swaying animation that rotates each plant slightly (2-3 degrees) left and right with a duration of 3-4 seconds, and apply it to the `.plant` class. Make sure the animation loops infinitely and has an easing function for natural movement.
---
Learn more about [agent mode](https://code.visualstudio.com/blogs/2025/02/24/introducing-copilot-agent-mode) here.
## 🚀 Challenge: Adding Glass Reflections
Ready to enhance your terrarium with realistic glass reflections? This technique will add depth and realism to the design.
## 🚀Challenge
Add a 'bubble' shine to the left bottom area of the jar to make it look more glasslike. You will be styling the `.jar-glossy-long` and `.jar-glossy-short` to look like a reflected shine. Here's how it would look:
You'll create subtle highlights that simulate how light reflects off glass surfaces. This approach is similar to how Renaissance painters like Jan van Eyck used light and reflection to make painted glass appear three-dimensional. Here's what you're aiming for:
![finished terrarium](./images/terrarium-final.png)
To complete the post-lecture quiz, go through this Learn module: [Style your HTML app with CSS](https://docs.microsoft.com/learn/modules/build-simple-website/4-css-basics/?WT.mc_id=academic-77807-sagibbon)
**Your challenge:**
- **Create** subtle white or light-colored oval shapes for the glass reflections
- **Position** them strategically on the left side of the jar
- **Apply** appropriate opacity and blur effects for realistic light reflection
- **Use** `border-radius` to create organic, bubble-like shapes
- **Experiment** with gradients or box-shadows for enhanced realism
## Post-Lecture Quiz
[Post-lecture quiz](https://ff-quizzes.netlify.app/web/quiz/18)
## Review & Self Study
## Expand Your CSS Knowledge
CSS can feel complex initially, but understanding these core concepts provides a solid foundation for more advanced techniques.
**Your next CSS learning areas:**
- **Flexbox** - simplifies alignment and distribution of elements
- **CSS Grid** - provides powerful tools for creating complex layouts
- **CSS Variables** - reduces repetition and improves maintainability
- **Responsive design** - ensures sites work well across different screen sizes
### Interactive Learning Resources
Practice these concepts with these engaging, hands-on games:
- 🐸 [Flexbox Froggy](https://flexboxfroggy.com/) - Master Flexbox through fun challenges
- 🌱 [Grid Garden](https://codepip.com/games/grid-garden/) - Learn CSS Grid by growing virtual carrots
- 🎯 [CSS Battle](https://cssbattle.dev/) - Test your CSS skills with coding challenges
### Additional Learning
CSS seems deceptively straightforward, but there are many challenges when trying to style an app perfectly for all browsers and all screen sizes. CSS-Grid and Flexbox are tools that have been developed to make the job a little more structured and more reliable. Learn about these tools by playing [Flexbox Froggy](https://flexboxfroggy.com/) and [Grid Garden](https://codepip.com/games/grid-garden/).
For comprehensive CSS fundamentals, complete this Microsoft Learn module: [Style your HTML app with CSS](https://docs.microsoft.com/learn/modules/build-simple-website/4-css-basics/?WT.mc_id=academic-77807-sagibbon)
## Assignment

@ -2,26 +2,119 @@
## Objective
Refactor the terrarium project to use **Flexbox** or **CSS Grid** for layout. Update the HTML and CSS as needed to achieve a modern, responsive design. You do not need to implement draggable elements—focus on layout and styling only.
Transform your terrarium project to use modern CSS layout techniques! Refactor the current absolute positioning approach to implement **Flexbox** or **CSS Grid** for a more maintainable, responsive design. This assignment challenges you to apply modern CSS standards while maintaining the visual appeal of your terrarium.
## Instructions
Understanding when and how to use different layout methods is a crucial skill for modern web development. This exercise bridges traditional positioning techniques with contemporary CSS layout systems.
1. **Create a new version** of the terrarium app. Update the markup and CSS to use Flexbox or CSS Grid for layout.
2. **Ensure the art and elements are in place** as in the original version.
3. **Test your design** in at least two different browsers (e.g., Chrome, Firefox, Edge).
4. **Take screenshots** of your terrarium in each browser to demonstrate cross-browser compatibility.
5. **Submit** your updated code and screenshots.
## Assignment Instructions
## Rubric
### Phase 1: Analysis and Planning
1. **Review your current terrarium code** - Identify which elements are currently using absolute positioning
2. **Choose your layout method** - Decide whether Flexbox or CSS Grid better suits your design goals
3. **Sketch your new layout structure** - Plan how containers and plant elements will be organized
| Criteria | Exemplary | Adequate | Needs Improvement |
|------------|--------------------------------------------------------------------------|---------------------------------------|----------------------------------------|
| Layout | Fully refactored using Flexbox or CSS Grid; visually appealing and responsive | Some elements refactored; partial use of Flexbox or Grid | Little or no use of Flexbox or Grid; layout unchanged |
| Cross-Browser | Screenshots provided for multiple browsers; consistent appearance | Screenshots for one browser; minor inconsistencies | No screenshots or major inconsistencies |
| Code Quality | Clean, well-organized HTML/CSS; clear comments | Some organization; few comments | Disorganized code; lacks comments |
### Phase 2: Implementation
1. **Create a new version** of your terrarium project in a separate folder
2. **Update the HTML structure** as needed to support your chosen layout method
3. **Refactor the CSS** to use Flexbox or CSS Grid instead of absolute positioning
4. **Maintain visual consistency** - Ensure your plants and terrarium jar appear in the same positions
5. **Implement responsive behavior** - Your layout should adapt gracefully to different screen sizes
## Tips
### Phase 3: Testing and Documentation
1. **Cross-browser testing** - Verify your design works in Chrome, Firefox, Edge, and Safari
2. **Responsive testing** - Check your layout on mobile, tablet, and desktop screen sizes
3. **Documentation** - Add comments to your CSS explaining your layout choices
4. **Screenshots** - Capture your terrarium in different browsers and screen sizes
- Review [Flexbox](https://css-tricks.com/snippets/css/a-guide-to-flexbox/) and [CSS Grid](https://css-tricks.com/snippets/css/complete-guide-grid/) guides.
- Use browser developer tools to test responsiveness.
- Comment your code for clarity.
## Technical Requirements
### Layout Implementation
- **Choose ONE**: Implement either Flexbox OR CSS Grid (not both for the same elements)
- **Responsive Design**: Use relative units (`rem`, `em`, `%`, `vw`, `vh`) instead of fixed pixels
- **Accessibility**: Maintain proper semantic HTML structure and alt text
- **Code Quality**: Use consistent naming conventions and organize CSS logically
### Modern CSS Features to Include
```css
/* Example Flexbox approach */
.terrarium-container {
display: flex;
flex-direction: column;
min-height: 100vh;
align-items: center;
justify-content: center;
}
.plant-containers {
display: flex;
justify-content: space-between;
width: 100%;
max-width: 1200px;
}
/* Example Grid approach */
.terrarium-layout {
display: grid;
grid-template-columns: 1fr 3fr 1fr;
grid-template-rows: auto 1fr;
min-height: 100vh;
gap: 1rem;
}
```
### Browser Support Requirements
- **Chrome/Edge**: Latest 2 versions
- **Firefox**: Latest 2 versions
- **Safari**: Latest 2 versions
- **Mobile browsers**: iOS Safari, Chrome Mobile
## Deliverables
1. **Updated HTML file** with improved semantic structure
2. **Refactored CSS file** using modern layout techniques
3. **Screenshot collection** showing cross-browser compatibility:
- Desktop view (1920x1080)
- Tablet view (768x1024)
- Mobile view (375x667)
- At least 2 different browsers
4. **README.md file** documenting:
- Your layout choice (Flexbox vs Grid) and reasoning
- Challenges faced during refactoring
- Browser compatibility notes
- Instructions for running your code
## Assessment Rubric
| Criteria | Exemplary (4) | Proficient (3) | Developing (2) | Beginning (1) |
|----------|---------------|----------------|---------------|---------------|
| **Layout Implementation** | Masterful use of Flexbox/Grid with advanced features; fully responsive | Correct implementation with good responsive behavior | Basic implementation with minor responsive issues | Incomplete or incorrect layout implementation |
| **Code Quality** | Clean, well-organized CSS with meaningful comments and consistent naming | Good organization with some comments | Adequate organization with minimal comments | Poor organization; difficult to understand |
| **Cross-Browser Compatibility** | Perfect consistency across all required browsers with screenshots | Good compatibility with minor differences documented | Some compatibility issues that don't break functionality | Major compatibility problems or missing testing |
| **Responsive Design** | Exceptional mobile-first approach with smooth breakpoints | Good responsive behavior with appropriate breakpoints | Basic responsive features with some layout issues | Limited or broken responsive behavior |
| **Documentation** | Comprehensive README with detailed explanations and insights | Good documentation covering all required elements | Basic documentation with minimal explanations | Incomplete or missing documentation |
## Helpful Resources
### Layout Method Guides
- 📖 [A Complete Guide to Flexbox](https://css-tricks.com/snippets/css/a-guide-to-flexbox/)
- 📖 [A Complete Guide to CSS Grid](https://css-tricks.com/snippets/css/complete-guide-grid/)
- 📖 [Flexbox vs Grid - Choose the Right Tool](https://blog.webdevsimplified.com/2022-11/flexbox-vs-grid/)
### Browser Testing Tools
- 🛠️ [Browser DevTools Responsive Mode](https://developer.chrome.com/docs/devtools/device-mode/)
- 🛠️ [Can I Use - Feature Support](https://caniuse.com/)
- 🛠️ [BrowserStack - Cross-browser Testing](https://www.browserstack.com/)
### Code Quality Tools
- ✅ [CSS Validator](https://jigsaw.w3.org/css-validator/)
- ✅ [HTML Validator](https://validator.w3.org/)
- ✅ [WebAIM Contrast Checker](https://webaim.org/resources/contrastchecker/)
## Bonus Challenges
🌟 **Advanced Layouts**: Implement both Flexbox AND Grid in different parts of your design
🌟 **Animation Integration**: Add CSS transitions or animations that work with your new layout
🌟 **Dark Mode**: Implement a CSS custom properties-based theme switcher
🌟 **Container Queries**: Use modern container query techniques for component-level responsiveness
> 💡 **Remember**: The goal isn't just to make it work, but to understand WHY your chosen layout method is the best solution for this particular design challenge!

@ -1,23 +1,41 @@
# Terrarium Project Part 3: DOM Manipulation and a Closure
# Terrarium Project Part 3: DOM Manipulation and JavaScript Closures
![DOM and a closure](../../sketchnotes/webdev101-js.png)
> Sketchnote by [Tomomi Imura](https://twitter.com/girlie_mac)
Welcome to one of the most engaging aspects of web development - making things interactive! The Document Object Model (DOM) is like a bridge between your HTML and JavaScript, and today we'll use it to bring your terrarium to life. When Tim Berners-Lee created the first web browser, he envisioned a web where documents could be dynamic and interactive - the DOM makes that vision possible.
We'll also explore JavaScript closures, which might sound intimidating initially. Think of closures as creating "memory pockets" where your functions can remember important information. It's like each plant in your terrarium having its own data record to track its position. By the end of this lesson, you'll understand how natural and useful they are.
Here's what we're building: a terrarium where users can drag and drop plants anywhere they want. You'll learn the DOM manipulation techniques that power everything from drag-and-drop file uploads to interactive games. Let's make your terrarium come alive.
## Pre-Lecture Quiz
[Pre-lecture quiz](https://ff-quizzes.netlify.app/web/quiz/19)
### Introduction
## Understanding the DOM: Your Gateway to Interactive Web Pages
The Document Object Model (DOM) is how JavaScript communicates with your HTML elements. When your browser loads an HTML page, it creates a structured representation of that page in memory - that's the DOM. Think of it as a family tree where every HTML element is a family member that JavaScript can access, modify, or rearrange.
DOM manipulation transforms static pages into interactive websites. Every time you see a button change color on hover, content update without page refresh, or elements you can drag around, that's DOM manipulation at work.
![DOM tree representation](./images/dom-tree.png)
> A representation of the DOM and the HTML markup that references it. From [Olfa Nasraoui](https://www.researchgate.net/publication/221417012_Profile-Based_Focused_Crawler_for_Social_Media-Sharing_Websites)
Manipulating the DOM, or the "Document Object Model", is a key aspect of web development. According to [MDN](https://developer.mozilla.org/docs/Web/API/Document_Object_Model/Introduction), "The Document Object Model (DOM) is the data representation of the objects that comprise the structure and content of a document on the web." The challenges around DOM manipulation on the web have often been the impetus behind using JavaScript frameworks instead of vanilla JavaScript to manage the DOM, but we will manage on our own!
**Here's what makes the DOM powerful:**
- **Provides** a structured way to access any element on your page
- **Enables** dynamic content updates without page refreshes
- **Allows** real-time response to user interactions like clicks and drags
- **Creates** the foundation for modern interactive web applications
In addition, this lesson will introduce the idea of a [JavaScript closure](https://developer.mozilla.org/docs/Web/JavaScript/Closures), which you can think of as a function enclosed by another function so that the inner function has access to the outer function's scope.
## JavaScript Closures: Creating Organized, Powerful Code
> JavaScript closures are a vast and complex topic. This lesson touches on the most basic idea that in this terrarium's code, you will find a closure: an inner function and an outer function constructed in a way to allow the inner function access to the outer function's scope. For much more information on how this works, please visit the [extensive documentation](https://developer.mozilla.org/docs/Web/JavaScript/Closures).
A [JavaScript closure](https://developer.mozilla.org/docs/Web/JavaScript/Closures) is like giving a function its own private workspace with persistent memory. Consider how Darwin's finches on the Galápagos Islands each developed specialized beaks based on their specific environment - closures work similarly, creating specialized functions that "remember" their specific context even after their parent function has finished.
We will use a closure to manipulate the DOM.
In our terrarium, closures help each plant remember its own position independently. This pattern appears throughout professional JavaScript development, making it a valuable concept to understand.
Think of the DOM as a tree, representing all the ways that a web page document can be manipulated. Various APIs (Application Program Interfaces) have been written so that programmers, using their programming language of choice, can access the DOM and edit, change, rearrange, and otherwise manage it.
> 💡 **Understanding Closures**: Closures are a significant topic in JavaScript, and many developers use them for years before fully grasping all the theoretical aspects. Today, we're focusing on practical application - you'll see closures naturally emerge as we build our interactive features. Understanding will develop as you see how they solve real problems.
![DOM tree representation](./images/dom-tree.png)
@ -25,28 +43,54 @@ Think of the DOM as a tree, representing all the ways that a web page document c
In this lesson, we will complete our interactive terrarium project by creating the JavaScript that will allow a user to manipulate the plants on the page.
### Prerequisite
## Before We Begin: Setting Up for Success
You should have the HTML and CSS for your terrarium built. By the end of this lesson you will be able to move the plants into and out of the terrarium by dragging them.
You'll need your HTML and CSS files from the previous terrarium lessons - we're about to make that static design interactive. If you're joining for the first time, completing those lessons first will provide important context.
### Task
Here's what we'll build:
- **Smooth drag-and-drop** for all terrarium plants
- **Coordinate tracking** so plants remember their positions
- **A complete interactive interface** using vanilla JavaScript
- **Clean, organized code** using closure patterns
In your terrarium folder, create a new file called `script.js`. Import that file in the `<head>` section:
## Setting Up Your JavaScript File
Let's create the JavaScript file that will make your terrarium interactive.
**Step 1: Create your script file**
In your terrarium folder, create a new file called `script.js`.
**Step 2: Link the JavaScript to your HTML**
Add this script tag to the `<head>` section of your `index.html` file:
```html
<script src="./script.js" defer></script>
<script src="./script.js" defer></script>
```
> Note: use `defer` when importing an external JavaScript file into the html file so as to allow the JavaScript to execute only after the HTML file has been fully loaded. You could also use the `async` attribute, which allows the script to execute while the HTML file is parsing, but in our case, it's important to have the HTML elements fully available for dragging before we allow the drag script to be executed.
**Why the `defer` attribute is important:**
- **Ensures** your JavaScript waits until all HTML is loaded
- **Prevents** errors where JavaScript looks for elements that aren't ready yet
- **Guarantees** all your plant elements are available for interaction
- **Provides** better performance than placing scripts at the page bottom
> ⚠️ **Important Note**: The `defer` attribute prevents common timing issues. Without it, JavaScript may try to access HTML elements before they're loaded, causing errors.
---
## The DOM elements
## Connecting JavaScript to Your HTML Elements
The first thing you need to do is to create references to the elements that you want to manipulate in the DOM. In our case, they are the 14 plants currently waiting in the side bars.
Before we can make elements draggable, JavaScript needs to locate them in the DOM. Think of this like a library cataloging system - once you have the catalog number, you can find exactly the book you need and access all its contents.
### Task
We'll use the `document.getElementById()` method to make these connections. It's like having a precise filing system - you provide an ID, and it locates exactly the element you need in your HTML.
```html
### Enabling Drag Functionality for All Plants
Add this code to your `script.js` file:
```javascript
// Enable drag functionality for all 14 plants
dragElement(document.getElementById('plant1'));
dragElement(document.getElementById('plant2'));
dragElement(document.getElementById('plant3'));
@ -63,135 +107,298 @@ dragElement(document.getElementById('plant13'));
dragElement(document.getElementById('plant14'));
```
What's going on here? You are referencing the document and looking through its DOM to find an element with a particular Id. Remember in the first lesson on HTML that you gave individual Ids to each plant image (`id="plant1"`)? Now you will make use of that effort. After identifying each element, you pass that item to a function called `dragElement` that you'll build in a minute. Thus, the element in the HTML is now drag-enabled, or will be shortly.
**Here's what this code accomplishes:**
- **Locates** each plant element in the DOM using its unique ID
- **Retrieves** a JavaScript reference to each HTML element
- **Passes** each element to a `dragElement` function (which we'll create next)
- **Prepares** every plant for drag-and-drop interaction
- **Connects** your HTML structure to JavaScript functionality
> 🎯 **Why Use IDs Instead of Classes?** IDs provide unique identifiers for specific elements, while CSS classes are designed for styling groups of elements. When JavaScript needs to manipulate individual elements, IDs offer the precision and performance we need.
✅ Why do we reference elements by Id? Why not by their CSS class? You might refer to the previous lesson on CSS to answer this question.
> 💡 **Pro Tip**: Notice how we're calling `dragElement()` for each plant individually. This approach ensures that each plant gets its own independent dragging behavior, which is essential for smooth user interaction.
---
## The Closure
## Building the Drag Element Closure
Now you are ready to create the dragElement closure, which is an outer function that encloses an inner function or functions (in our case, we will have three).
Now we'll create the heart of our dragging functionality: a closure that manages the dragging behavior for each plant. This closure will contain multiple inner functions that work together to track mouse movements and update element positions.
Closures are useful when one or more functions need to access an outer function's scope. Here's an example:
Closures are perfect for this task because they allow us to create "private" variables that persist between function calls, giving each plant its own independent coordinate tracking system.
### Understanding Closures with a Simple Example
Let me demonstrate closures with a simple example that illustrates the concept:
```javascript
function displayCandy(){
let candy = ['jellybeans'];
function addCandy(candyType) {
candy.push(candyType)
}
addCandy('gumdrops');
function createCounter() {
let count = 0; // This is like a private variable
function increment() {
count++; // The inner function remembers the outer variable
return count;
}
return increment; // We're giving back the inner function
}
displayCandy();
console.log(candy)
const myCounter = createCounter();
console.log(myCounter()); // 1
console.log(myCounter()); // 2
```
In this example, the displayCandy function surrounds a function that pushes a new candy type into an array that already exists in the function. If you were to run this code, the `candy` array would be undefined, as it is a local variable (local to the closure).
**Here's what's happening in this closure pattern:**
- **Creates** a private `count` variable that only exists within this closure
- **The inner function** can access and modify that outer variable (the closure mechanism)
- **When we return** the inner function, it maintains its connection to that private data
- **Even after** `createCounter()` finishes execution, `count` persists and remembers its value
### Why Closures Are Perfect for Drag Functionality
✅ How can you make the `candy` array accessible? Try moving it outside the closure. This way, the array becomes global, rather than remaining only available to the closure's local scope.
For our terrarium, each plant needs to remember its current position coordinates. Closures provide the perfect solution:
### Task
**Key benefits for our project:**
- **Maintains** private position variables for each plant independently
- **Preserves** coordinate data between drag events
- **Prevents** variable conflicts between different draggable elements
- **Creates** clean, organized code structure
Under the element declarations in `script.js`, create a function:
> 🎯 **Learning Goal**: You don't need to master every aspect of closures right now. Focus on seeing how they help us organize code and maintain state for our dragging functionality.
### Creating the dragElement Function
Now let's build the main function that will handle all the dragging logic. Add this function below your plant element declarations:
```javascript
function dragElement(terrariumElement) {
//set 4 positions for positioning on the screen
let pos1 = 0,
pos2 = 0,
pos3 = 0,
pos4 = 0;
terrariumElement.onpointerdown = pointerDrag;
// Initialize position tracking variables
let pos1 = 0, // Previous mouse X position
pos2 = 0, // Previous mouse Y position
pos3 = 0, // Current mouse X position
pos4 = 0; // Current mouse Y position
// Set up the initial drag event listener
terrariumElement.onpointerdown = pointerDrag;
}
```
`dragElement` get its `terrariumElement` object from the declarations at the top of the script. Then, you set some local positions at `0` for the object passed into the function. These are the local variables that will be manipulated for each element as you add drag and drop functionality within the closure to each element. The terrarium will be populated by these dragged elements, so the application needs to keep track of where they are placed.
**Understanding the position tracking system:**
- **`pos1` and `pos2`**: Store the difference between old and new mouse positions
- **`pos3` and `pos4`**: Track the current mouse coordinates
- **`terrariumElement`**: The specific plant element we're making draggable
- **`onpointerdown`**: The event that triggers when the user starts dragging
**Here's how the closure pattern works:**
- **Creates** private position variables for each plant element
- **Maintains** these variables throughout the dragging lifecycle
- **Ensures** each plant tracks its own coordinates independently
- **Provides** a clean interface through the `dragElement` function
### Why Use Pointer Events?
In addition, the terrariumElement that is passed to this function is assigned a `pointerdown` event, which is part of the [web APIs](https://developer.mozilla.org/docs/Web/API) designed to help with DOM management. `onpointerdown` fires when a button is pushed, or in our case, a draggable element is touched. This event handler works on both [web and mobile browsers](https://caniuse.com/?search=onpointerdown), with a few exceptions.
You might wonder why we use `onpointerdown` instead of the more familiar `onclick`. Here's the reasoning:
✅ The [event handler `onclick`](https://developer.mozilla.org/docs/Web/API/GlobalEventHandlers/onclick) has much more support cross-browser; why wouldn't you use it here? Think about the exact type of screen interaction you're trying to create here.
| Event Type | Best For | The Catch |
|------------|----------|-------------|
| `onclick` | Simple button clicks | Can't handle dragging (just clicks and releases) |
| `onpointerdown` | Both mouse and touch | Newer, but well-supported these days |
| `onmousedown` | Desktop mouse only | Leaves mobile users out in the cold |
**Why pointer events are perfect for what we're building:**
- **Works great** whether someone's using a mouse, finger, or even a stylus
- **Feels the same** on a laptop, tablet, or phone
- **Handles** the actual dragging motion (not just click-and-done)
- **Creates** a smooth experience that users expect from modern web apps
> 💡 **Future-Proofing**: Pointer events are the modern way to handle user interactions. Instead of writing separate code for mouse and touch, you get both for free. Pretty neat, right?
---
## The Pointerdrag function
## The pointerDrag Function: Capturing the Start of a Drag
The `terrariumElement` is ready to be dragged around; when the `onpointerdown` event is fired, the function `pointerDrag` is invoked. Add that function right under this line: `terrariumElement.onpointerdown = pointerDrag;`:
When a user presses down on a plant (whether with a mouse click or finger touch), the `pointerDrag` function springs into action. This function captures the initial coordinates and sets up the dragging system.
### Task
Add this function inside your `dragElement` closure, right after the line `terrariumElement.onpointerdown = pointerDrag;`:
```javascript
function pointerDrag(e) {
e.preventDefault();
console.log(e);
pos3 = e.clientX;
pos4 = e.clientY;
// Prevent default browser behavior (like text selection)
e.preventDefault();
// Capture the initial mouse/touch position
pos3 = e.clientX; // X coordinate where drag started
pos4 = e.clientY; // Y coordinate where drag started
// Set up event listeners for the dragging process
document.onpointermove = elementDrag;
document.onpointerup = stopElementDrag;
}
```
Several things happen. First, you prevent the default events that normally happen on pointerdown from occurring by using `e.preventDefault();`. This way you have more control over the interface's behavior.
**Step by step, here's what's happening:**
- **Prevents** default browser behaviors that could interfere with dragging
- **Records** the exact coordinates where the user started the drag gesture
- **Establishes** event listeners for the ongoing drag movement
- **Prepares** the system to track mouse/finger movement across the entire document
> Come back to this line when you've built the script file completely and try it without `e.preventDefault()` - what happens?
### Understanding Event Prevention
Second, open `index.html` in a browser window, and inspect the interface. When you click a plant, you can see how the 'e' event is captured. Dig into the event to see how much information is gathered by one pointer down event!
The `e.preventDefault()` line is crucial for smooth dragging:
Next, note how the local variables `pos3` and `pos4` are set to e.clientX. You can find the `e` values in the inspection pane. These values capture the x and y coordinates of the plant at the moment you click on it or touch it. You will need fine-grained control over the behavior of the plants as you click and drag them, so you keep track of their coordinates.
**Without prevention, browsers might:**
- **Select** text when dragging across the page
- **Trigger** context menus on right-click drag
- **Interfere** with our custom dragging behavior
- **Create** visual artifacts during the drag operation
✅ Is it becoming more clear why this entire app is built with one big closure? If it wasn't, how would you maintain scope for each of the 14 draggable plants?
> 🔍 **Experiment**: After completing this lesson, try removing `e.preventDefault()` and see how it affects the dragging experience. You'll quickly understand why this line is essential!
Complete the initial function by adding two more pointer event manipulations under `pos4 = e.clientY`:
### Coordinate Tracking System
```html
The `e.clientX` and `e.clientY` properties give us precise mouse/touch coordinates:
| Property | What It Measures | Use Case |
|----------|------------------|----------|
| `clientX` | Horizontal position relative to the viewport | Tracking left-right movement |
| `clientY` | Vertical position relative to the viewport | Tracking up-down movement |
**Understanding these coordinates:**
- **Provides** pixel-perfect positioning information
- **Updates** in real-time as the user moves their pointer
- **Remains** consistent across different screen sizes and zoom levels
- **Enables** smooth, responsive drag interactions
### Setting Up Document-Level Event Listeners
Notice how we attach the move and stop events to the entire `document`, not just the plant element:
```javascript
document.onpointermove = elementDrag;
document.onpointerup = stopElementDrag;
```
Now you are indicating that you want the plant to be dragged along with the pointer as you move it, and for the dragging gesture to stop when you deselect the plant. `onpointermove` and `onpointerup` are all parts of the same API as `onpointerdown`. The interface will throw errors now as you have not yet defined the `elementDrag` and the `stopElementDrag` functions, so build those out next.
## The elementDrag and stopElementDrag functions
**Why attach to the document:**
- **Continues** tracking even when the mouse leaves the plant element
- **Prevents** drag interruption if the user moves quickly
- **Provides** smooth dragging across the entire screen
- **Handles** edge cases where the cursor moves outside the browser window
> ⚡ **Performance Note**: We'll clean up these document-level listeners when dragging stops to avoid memory leaks and performance issues.
You will complete your closure by adding two more internal functions that will handle what happens when you drag a plant and stop dragging it. The behavior you want is that you can drag any plant at any time and place it anywhere on the screen. This interface is quite un-opinionated (there is no drop zone for example) to allow you to design your terrarium exactly as you like it by adding, removing, and repositioning plants.
## Completing the Drag System: Movement and Cleanup
### Task
Now we'll add the two remaining functions that handle the actual dragging movement and the cleanup when dragging stops. These functions work together to create smooth, responsive plant movement across your terrarium.
### The elementDrag Function: Tracking Movement
Add the `elementDrag` function right after the closing curly bracket of `pointerDrag`:
```javascript
function elementDrag(e) {
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
console.log(pos1, pos2, pos3, pos4);
terrariumElement.style.top = terrariumElement.offsetTop - pos2 + 'px';
terrariumElement.style.left = terrariumElement.offsetLeft - pos1 + 'px';
// Calculate the distance moved since the last event
pos1 = pos3 - e.clientX; // Horizontal distance moved
pos2 = pos4 - e.clientY; // Vertical distance moved
// Update the current position tracking
pos3 = e.clientX; // New current X position
pos4 = e.clientY; // New current Y position
// Apply the movement to the element's position
terrariumElement.style.top = (terrariumElement.offsetTop - pos2) + 'px';
terrariumElement.style.left = (terrariumElement.offsetLeft - pos1) + 'px';
}
```
In this function, you do a lot of editing of the initial positions 1-4 that you set as local variables in the outer function. What's going on here?
As you drag, you reassign `pos1` by making it equal to `pos3` (which you set earlier as `e.clientX`) minus the current `e.clientX` value. You do a similar operation to `pos2`. Then, you reset `pos3` and `pos4` to the new X and Y coordinates of the element. You can watch these changes in the console as you drag. Then, you manipulate the plant's css style to set its new position based on the new positions of `pos1` and `pos2`, calculating the plant's top and left X and Y coordinates based on comparing its offset with these new positions.
> `offsetTop` and `offsetLeft` are CSS properties that set an element's position based on that of its parent; its parent can be any element that is not positioned as `static`.
All this recalculation of positioning allows you to fine-tune the behavior of the terrarium and its plants.
**Understanding the coordinate mathematics:**
- **`pos1` and `pos2`**: Calculate how far the mouse has moved since the last update
- **`pos3` and `pos4`**: Store the current mouse position for the next calculation
- **`offsetTop` and `offsetLeft`**: Get the element's current position on the page
- **Subtraction logic**: Moves the element by the same amount the mouse moved
**Here's the movement calculation breakdown:**
1. **Measures** the difference between old and new mouse positions
2. **Calculates** how much to move the element based on mouse movement
3. **Updates** the element's CSS position properties in real-time
4. **Stores** the new position as the baseline for the next movement calculation
### Visual Representation of the Math
```mermaid
sequenceDiagram
participant Mouse
participant JavaScript
participant Plant
Mouse->>JavaScript: Move from (100,50) to (110,60)
JavaScript->>JavaScript: Calculate: moved 10px right, 10px down
JavaScript->>Plant: Update position by +10px right, +10px down
Plant->>Plant: Render at new position
```
### Task
### The stopElementDrag Function: Cleaning Up
The final task to complete the interface is to add the `stopElementDrag` function after the closing curly bracket of `elementDrag`:
Add the cleanup function after the closing curly bracket of `elementDrag`:
```javascript
function stopElementDrag() {
document.onpointerup = null;
document.onpointermove = null;
// Remove the document-level event listeners
document.onpointerup = null;
document.onpointermove = null;
}
```
This small function resets the `onpointerup` and `onpointermove` events so that you can either restart your plant's progress by starting to drag it again, or start dragging a new plant.
**Why cleanup is essential:**
- **Prevents** memory leaks from lingering event listeners
- **Stops** the dragging behavior when the user releases the plant
- **Allows** other elements to be dragged independently
- **Resets** the system for the next drag operation
**What happens without cleanup:**
- Event listeners continue running even after dragging stops
- Performance degrades as unused listeners accumulate
- Unexpected behavior when interacting with other elements
- Browser resources are wasted on unnecessary event handling
### Understanding CSS Position Properties
Our dragging system manipulates two key CSS properties:
| Property | What It Controls | How We Use It |
|----------|------------------|---------------|
| `top` | Distance from the top edge | Vertical positioning during drag |
| `left` | Distance from the left edge | Horizontal positioning during drag |
**Key insights about offset properties:**
- **`offsetTop`**: Current distance from the top of the positioned parent element
- **`offsetLeft`**: Current distance from the left of the positioned parent element
- **Positioning context**: These values are relative to the nearest positioned ancestor
- **Real-time updates**: Changes immediately when we modify the CSS properties
> 🎯 **Design Philosophy**: This drag system is intentionally flexible there are no "drop zones" or restrictions. Users can place plants anywhere, giving them complete creative control over their terrarium design.
✅ What happens if you don't set these events to null?
## Bringing It All Together: Your Complete Drag System
Now you have completed your project!
Congratulations! You've just built a sophisticated drag-and-drop system using vanilla JavaScript. Your complete `dragElement` function now contains a powerful closure that manages:
🥇Congratulations! You have finished your beautiful terrarium. ![finished terrarium](./images/terrarium-final.png)
**What your closure accomplishes:**
- **Maintains** private position variables for each plant independently
- **Handles** the complete drag lifecycle from start to finish
- **Provides** smooth, responsive movement across the entire screen
- **Cleans** up resources properly to prevent memory leaks
- **Creates** an intuitive, creative interface for terrarium design
### Testing Your Interactive Terrarium
Now test your interactive terrarium! Open your `index.html` file in a web browser and try the functionality:
1. **Click and hold** any plant to start dragging
2. **Move your mouse or finger** and watch the plant follow smoothly
3. **Release** to drop the plant in its new position
4. **Experiment** with different arrangements to explore the interface
🥇 **Achievement**: You've created a fully interactive web application using core concepts that professional developers use daily. That drag-and-drop functionality uses the same principles behind file uploads, kanban boards, and many other interactive interfaces.
![finished terrarium](./images/terrarium-final.png)
---
@ -203,21 +410,66 @@ Use the Agent mode to complete the following challenge:
**Prompt:** Create a reset button that, when clicked, animates all plants back to their original sidebar positions using CSS transitions. The function should store the original positions when the page loads and smoothly transition plants back to those positions over 1 second when the reset button is pressed.
## 🚀Challenge
Learn more about [agent mode](https://code.visualstudio.com/blogs/2025/02/24/introducing-copilot-agent-mode) here.
## 🚀 Additional Challenge: Expand Your Skills
Ready to take your terrarium to the next level? Try implementing these enhancements:
**Creative Extensions:**
- **Double-click** a plant to bring it to the front (z-index manipulation)
- **Add visual feedback** like a subtle glow when hovering over plants
- **Implement boundaries** to prevent plants from being dragged outside the terrarium
- **Create a save function** that remembers plant positions using localStorage
- **Add sound effects** for picking up and placing plants
Add new event handler to your closure to do something more to the plants; for example, double-click a plant to bring it to the front. Get creative!
> 💡 **Learning Opportunity**: Each of these challenges will teach you new aspects of DOM manipulation, event handling, and user experience design.
## Post-Lecture Quiz
[Post-lecture quiz](https://ff-quizzes.netlify.app/web/quiz/20)
## Review & Self Study
## Review & Self Study: Deepening Your Understanding
You've mastered the fundamentals of DOM manipulation and closures, but there's always more to explore! Here are some pathways to expand your knowledge and skills.
### Alternative Drag and Drop Approaches
We used pointer events for maximum flexibility, but web development offers multiple approaches:
| Approach | Best For | Learning Value |
|----------|----------|----------------|
| [HTML Drag and Drop API](https://developer.mozilla.org/docs/Web/API/HTML_Drag_and_Drop_API) | File uploads, formal drag zones | Understanding native browser capabilities |
| [Touch Events](https://developer.mozilla.org/docs/Web/API/Touch_events) | Mobile-specific interactions | Mobile-first development patterns |
| CSS `transform` properties | Smooth animations | Performance optimization techniques |
### Advanced DOM Manipulation Topics
**Next steps in your learning journey:**
- **Event delegation**: Handling events efficiently for multiple elements
- **Intersection Observer**: Detecting when elements enter/leave the viewport
- **Mutation Observer**: Watching for changes in the DOM structure
- **Web Components**: Creating reusable, encapsulated UI elements
- **Virtual DOM concepts**: Understanding how frameworks optimize DOM updates
### Essential Resources for Continued Learning
**Technical Documentation:**
- [MDN Pointer Events Guide](https://developer.mozilla.org/docs/Web/API/Pointer_events) - Comprehensive pointer event reference
- [W3C Pointer Events Specification](https://www.w3.org/TR/pointerevents1/) - Official standards documentation
- [JavaScript Closures Deep Dive](https://developer.mozilla.org/docs/Web/JavaScript/Closures) - Advanced closure patterns
While dragging elements around the screen seems trivial, there are many ways to do this and many pitfalls, depending on the effect you seek. In fact, there is an entire [drag and drop API](https://developer.mozilla.org/docs/Web/API/HTML_Drag_and_Drop_API) that you can try. We didn't use it in this module because the effect we wanted was somewhat different, but try this API on your own project and see what you can achieve.
**Browser Compatibility:**
- [CanIUse.com](https://caniuse.com/) - Check feature support across browsers
- [MDN Browser Compatibility Data](https://github.com/mdn/browser-compat-data) - Detailed compatibility information
Find more information on pointer events on the [W3C docs](https://www.w3.org/TR/pointerevents1/) and on [MDN web docs](https://developer.mozilla.org/docs/Web/API/Pointer_events).
**Practice Opportunities:**
- **Build** a puzzle game using similar drag mechanics
- **Create** a kanban board with drag-and-drop task management
- **Design** an image gallery with draggable photo arrangements
- **Experiment** with touch gestures for mobile interfaces
Always check browser capabilities using [CanIUse.com](https://caniuse.com/).
> 🎯 **Learning Strategy**: The best way to solidify these concepts is through practice. Try building variations of draggable interfaces each project will teach you something new about user interaction and DOM manipulation.
## Assignment

@ -1,11 +1,123 @@
# Work a bit more with the DOM
# DOM Element Investigation Assignment
## Overview
Now that you've experienced the power of DOM manipulation firsthand, it's time to explore the broader world of DOM interfaces. This assignment will deepen your understanding of how different web technologies interact with the DOM beyond just dragging elements.
## Learning Objectives
By completing this assignment, you will:
- **Research** and understand a specific DOM interface in depth
- **Analyze** real-world implementations of DOM manipulation
- **Connect** theoretical concepts to practical applications
- **Develop** skills in technical documentation and analysis
## Instructions
Research the DOM a little more by 'adopting' a DOM element. Visit the MDN's [list of DOM interfaces](https://developer.mozilla.org/docs/Web/API/Document_Object_Model) and pick one. Find it being used on a web site in the web, and write an explanation of how it is used.
### Step 1: Choose Your DOM Interface
Visit MDN's comprehensive [list of DOM interfaces](https://developer.mozilla.org/docs/Web/API/Document_Object_Model) and select one interface that interests you. Consider choosing from these categories for variety:
**Beginner-Friendly Options:**
- `Element.classList` - Managing CSS classes dynamically
- `Document.querySelector()` - Advanced element selection
- `Element.addEventListener()` - Event handling beyond pointer events
- `Window.localStorage` - Client-side data storage
**Intermediate Challenges:**
- `Intersection Observer API` - Detecting element visibility
- `MutationObserver` - Watching DOM changes
- `Drag and Drop API` - Alternative to our pointer-based approach
- `Geolocation API` - Accessing user location
**Advanced Exploration:**
- `Web Components` - Custom elements and shadow DOM
- `Canvas API` - Programmatic graphics
- `Web Workers` - Background processing
- `Service Workers` - Offline functionality
### Step 2: Research and Document
Create a comprehensive analysis (300-500 words) that includes:
#### Technical Overview
- **Define** what your chosen interface does in clear, beginner-friendly language
- **Explain** the key methods, properties, or events it provides
- **Describe** the types of problems it's designed to solve
#### Real-World Implementation
- **Find** a website that uses your chosen interface (inspect the code or research examples)
- **Document** the specific implementation with code snippets if possible
- **Analyze** why the developers chose this approach
- **Explain** how it enhances the user experience
#### Practical Application
- **Compare** your interface to the techniques we used in the terrarium project
- **Suggest** how your interface could enhance or extend the terrarium functionality
- **Identify** other projects where this interface would be valuable
### Step 3: Code Example
Include a simple, working code example that demonstrates your interface in action. This should be:
- **Functional** - The code should actually work when tested
- **Commented** - Explain what each part does
- **Relevant** - Connected to a realistic use case
- **Beginner-friendly** - Understandable to someone learning web development
## Submission Format
Structure your submission with clear headings:
```markdown
# [Interface Name] DOM Investigation
## What It Does
[Technical overview]
## Real-World Example
[Website analysis and implementation details]
## Code Demonstration
[Your working example with comments]
## Reflection
[How this connects to our terrarium project and future applications]
```
## Assessment Rubric
| Criteria | Exemplary (A) | Proficient (B) | Developing (C) | Needs Improvement (D) |
|----------|---------------|----------------|----------------|----------------------|
| **Technical Understanding** | Demonstrates deep understanding with accurate explanations and proper terminology | Shows solid understanding with mostly accurate explanations | Basic understanding with some misconceptions | Limited understanding with significant errors |
| **Real-World Analysis** | Identifies and thoroughly analyzes actual implementation with specific examples | Finds real example with adequate analysis | Locates example but analysis lacks depth | Vague or inaccurate real-world connection |
| **Code Example** | Working, well-commented code that clearly demonstrates the interface | Functional code with adequate comments | Code works but needs better documentation | Code has errors or poor explanation |
| **Writing Quality** | Clear, engaging writing with proper structure and technical communication | Well-organized with good technical writing | Adequate organization and clarity | Poor organization or unclear communication |
| **Critical Thinking** | Makes insightful connections between concepts and suggests innovative applications | Shows good analytical thinking and relevant connections | Some analysis present but could be deeper | Limited evidence of critical thinking |
## Tips for Success
**Research Strategies:**
- **Start** with MDN documentation for authoritative information
- **Look** for code examples on GitHub or CodePen
- **Check** popular websites using browser developer tools
- **Watch** tutorial videos for visual explanations
**Writing Guidelines:**
- **Use** your own words rather than copying documentation
- **Include** specific examples and code snippets
- **Explain** technical concepts as if teaching a friend
- **Connect** your interface to broader web development concepts
**Code Example Ideas:**
- **Create** a simple demo that showcases the interface's main features
- **Build** on concepts from our terrarium project where relevant
- **Focus** on functionality over visual design
- **Test** your code to ensure it works correctly
## Submission Deadline
[Insert deadline here]
## Rubric
## Questions?
| Criteria | Exemplary | Adequate | Needs Improvement |
| -------- | --------------------------------------------- | ------------------------------------------------ | ----------------------- |
| | Paragraph write-up is presented, with example | Paragraph write-up is presented, without example | No writeup is presented |
If you need clarification on any aspect of this assignment, don't hesitate to ask! This investigation will deepen your understanding of how the DOM enables the interactive web experiences we use every day.

@ -2,26 +2,30 @@
## Introduction
Typing is one of the most underrated skills of the developer. The ability to quickly transfer thoughts from your head to your editor allows creativity to flow freely. One of the best ways to learn is to play a game!
Here's something every developer knows but rarely talks about: typing fast is a superpower! 🚀 Think about it - the faster you can get your ideas from your brain to your code editor, the more your creativity can flow. It's like having a direct pipeline between your thoughts and the screen.
> So, let's build a typing game!
Want to know one of the best ways to level up this skill? You guessed it - we're going to build a game!
You're going to use the JavaScript, HTML and CSS skills you have built up so far to create a typing game. The game will present the player with a random quote (we're using [Sherlock Holmes](https://en.wikipedia.org/wiki/Sherlock_Holmes) quotes) and time how long the player takes to type it out accurately. You're going to use the JavaScript, HTML and CSS skills you have built up so far to create a typing game.
> Let's create an awesome typing game together!
Ready to put all those JavaScript, HTML, and CSS skills you've been learning to work? We're going to build a typing game that'll challenge you with random quotes from the legendary detective [Sherlock Holmes](https://en.wikipedia.org/wiki/Sherlock_Holmes). The game will track how fast and accurately you can type - and trust me, it's more addictive than you might think!
![demo](images/demo.gif)
## Prerequisites
## What You'll Need to Know
This lesson assumes you're familiar with the following concepts:
Before we dive in, make sure you're comfortable with these concepts (don't worry if you need a quick refresher - we've all been there!):
- Creating text input and button controls
- CSS and setting styles using classes
- CSS and setting styles using classes
- JavaScript basics
- Creating an array
- Creating a random number
- Getting the current time
## Lesson
If any of these feel a bit rusty, that's totally fine! Sometimes the best way to solidify your knowledge is by jumping into a project and figuring things out as you go.
## Let's Build This Thing!
[Creating a typing game by using event driven programming](./typing-game/README.md)

@ -1,47 +1,94 @@
# Creating a game using events
Have you ever wondered how websites know when you click a button or type in a text box? That's the magic of event-driven programming! What better way to learn this essential skill than by building something useful - a typing speed game that reacts to every keystroke you make.
You're going to see firsthand how web browsers "talk" to your JavaScript code. Every time you click, type, or move your mouse, the browser is sending little messages (we call them events) to your code, and you get to decide how to respond!
By the time we're done here, you'll have built a real typing game that tracks your speed and accuracy. More importantly, you'll understand the fundamental concepts that power every interactive website you've ever used. Let's dive in!
## Pre-Lecture Quiz
[Pre-lecture quiz](https://ff-quizzes.netlify.app/web/quiz/21)
## Event driven programming
When creating a browser based application, we provide a graphical user interface (GUI) for the user to use when interacting with what we've built. The most common way to interact with the browser is through clicking and typing in various elements. The challenge we face as a developer is we don't know when they're going to perform these operations!
Think about your favorite app or website - what makes it feel alive and responsive? It's all about how it reacts to what you do! Every tap, click, swipe, or keystroke creates what we call an "event," and that's where the real magic of web development happens.
Here's what makes programming for the web so interesting: we never know when someone will click that button or start typing in a text box. They might click immediately, wait five minutes, or maybe never click at all! This unpredictability means we need to think differently about how we write our code.
Instead of writing code that runs from top to bottom like a recipe, we write code that sits patiently waiting for something to happen. It's similar to how telegraph operators in the 1800s would sit by their machines, ready to respond the moment a message came through the wire.
So what exactly is an "event"? Simply put, it's something that happens! When you click a button - that's an event. When you type a letter - that's an event. When you move your mouse - that's another event.
[Event-driven programming](https://en.wikipedia.org/wiki/Event-driven_programming) is the name for the type of programming we need to do to create our GUI. If we break this phrase down a little bit, we see the core word here is **event**. [Event](https://www.merriam-webster.com/dictionary/event), according to Merriam-Webster, is defined as "something which happens". This describes our situation perfectly. We know something is going to happen for which we want to execute some code in response, but we don't know when it will take place.
Event-driven programming lets us set up our code to listen and respond. We create special functions called **event listeners** that wait patiently for specific things to happen, then spring into action when they do.
The way we mark a section of code we want to execute is by creating a function. When we think about [procedural programming](https://en.wikipedia.org/wiki/Procedural_programming), functions are called in a specific order. This same thing is going to be true with event driven programming. The difference is **how** the functions will be called.
Think of event listeners like having a doorbell for your code. You set up the doorbell (`addEventListener()`), tell it what sound to listen for (like a 'click' or 'keypress'), and then specify what should happen when someone rings it (your custom function).
To handle events (button clicking, typing, etc.), we register **event listeners**. An event listener is a function which listens for an event to occur and executes in response. Event listeners can update the UI, make calls to the server, or whatever else needs to be done in response to the user's action. We add an event listener by using [addEventListener](https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener), and providing a function to execute.
**Here's how event listeners work:**
- **Listens** for specific user actions like clicks, keystrokes, or mouse movements
- **Executes** your custom code when the specified event occurs
- **Responds** immediately to user interactions, creating a seamless experience
- **Handles** multiple events on the same element using different listeners
> **NOTE:** It's worth highlighting there are numerous ways to create event listeners. You can use anonymous functions, or create named ones. You can use various shortcuts, like setting the `click` property, or using `addEventListener`. In our exercise we are going to focus on `addEventListener` and anonymous functions, as it's probably the most common technique web developers use. It's also the most flexible, as `addEventListener` works for all events, and the event name can be provided as a parameter.
> **NOTE:** It's worth highlighting there are numerous ways to create event listeners. You can use anonymous functions, or create named ones. You can use various shortcuts, like setting the `click` property, or using `addEventListener()`. In our exercise we are going to focus on `addEventListener()` and anonymous functions, as it's probably the most common technique web developers use. It's also the most flexible, as `addEventListener()` works for all events, and the event name can be provided as a parameter.
### Common events
While web browsers offer dozens of different events you can listen for, most interactive applications rely on just a handful of essential events. Understanding these core events will give you the foundation to build sophisticated user interactions.
There are [dozens of events](https://developer.mozilla.org/docs/Web/Events) available for you to listen to when creating an application. Basically anything a user does on a page raises an event, which gives you a lot of power to ensure they get the experience you desire. Fortunately, you'll normally only need a small handful of events. Here's a few common ones (including the two we'll use when creating our game):
- [click](https://developer.mozilla.org/docs/Web/API/Element/click_event): The user clicked on something, typically a button or hyperlink
- [contextmenu](https://developer.mozilla.org/docs/Web/API/Element/contextmenu_event): The user clicked the right mouse button
- [select](https://developer.mozilla.org/docs/Web/API/Element/select_event): The user highlighted some text
- [input](https://developer.mozilla.org/docs/Web/API/Element/input_event): The user input some text
| Event | Description | Common Use Cases |
|-------|-------------|------------------|
| `click` | The user clicked on something | Buttons, links, interactive elements |
| `contextmenu` | The user clicked the right mouse button | Custom right-click menus |
| `select` | The user highlighted some text | Text editing, copy operations |
| `input` | The user input some text | Form validation, real-time search |
**Understanding these event types:**
- **Triggers** when users interact with specific elements on your page
- **Provides** detailed information about the user's action through event objects
- **Enables** you to create responsive, interactive web applications
- **Works** consistently across different browsers and devices
## Creating the game
We are going to create a game to explore how events work in JavaScript. Our game is going to test a player's typing skill, which is one of the most underrated skills all developers should have. We should all be practicing our typing! The general flow of the game will look like this:
Now that you understand how events work, let's put that knowledge into practice by building something useful. We'll create a typing speed game that demonstrates event handling while helping you develop an important developer skill.
We're going to create a game to explore how events work in JavaScript. Our game will test a player's typing skill, which is one of the most underrated skills all developers should have. Fun fact: the QWERTY keyboard layout we use today was actually designed in the 1870s for typewriters - and good typing skills are still just as valuable for programmers today! The general flow of the game will look like this:
```mermaid
flowchart TD
A[Player clicks Start] --> B[Random quote displays]
B --> C[Player types in textbox]
C --> D{Word complete?}
D -->|Yes| E[Highlight next word]
D -->|No| F{Correct so far?}
F -->|Yes| G[Keep normal styling]
F -->|No| H[Show error styling]
E --> I{Quote complete?}
I -->|No| C
I -->|Yes| J[Show success message with time]
G --> C
H --> C
```
- Player clicks on start button and is presented with a quote to type
- Player types the quote as quickly as they can in a textbox
- As each word is completed, the next one is highlighted
- If the player has a typo, the textbox is updated to red
- When the player completes the quote, a success message is displayed with the elapsed time
**Here's how our game will work:**
- **Starts** when the player clicks the start button and displays a random quote
- **Tracks** the player's typing progress word by word in real-time
- **Highlights** the current word to guide the player's focus
- **Provides** immediate visual feedback for typing errors
- **Calculates** and displays the total time when the quote is completed
Let's build our game, and learn about events!
### File structure
We're going to need three total files: **index.html**, **script.js** and **style.css**. Let's start by setting those up to make life a little easier for us.
Before we start coding, let's get organized! Having a clean file structure from the beginning will save you headaches later and make your project more professional. 😊
- Create a new folder for your work by opening a console or terminal window and issuing the following command:
We're going to keep things simple with just three files: `index.html` for our page structure, `script.js` for all our game logic, and `style.css` to make everything look great. This is the classic trio that powers most of the web!
**Create a new folder for your work by opening a console or terminal window and issuing the following command:**
```bash
# Linux or macOS
@ -51,29 +98,49 @@ mkdir typing-game && cd typing-game
md typing-game && cd typing-game
```
- Open Visual Studio Code
**Here's what these commands do:**
- **Creates** a new directory called `typing-game` for your project files
- **Navigates** into the newly created directory automatically
- **Sets up** a clean workspace for your game development
**Open Visual Studio Code:**
```bash
code .
```
- Add three files to the folder in Visual Studio Code with the following names:
- index.html
- script.js
- style.css
**This command:**
- **Launches** Visual Studio Code in the current directory
- **Opens** your project folder in the editor
- **Provides** access to all the development tools you'll need
**Add three files to the folder in Visual Studio Code with the following names:**
- `index.html` - Contains the structure and content of your game
- `script.js` - Handles all the game logic and event listeners
- `style.css` - Defines the visual appearance and styling
## Create the user interface
If we explore the requirements, we know we're going to need a handful of elements on our HTML page. This is sort of like a recipe, where we need some ingredients:
Now let's build the stage where all our game action will happen! Think of this like designing the control panel for a spaceship - we need to make sure everything our players need is right where they expect it to be.
Let's figure out what our game actually needs. If you were playing a typing game, what would you want to see on the screen? Here's what we'll need:
| UI Element | Purpose | HTML Element |
|------------|---------|-------------|
| Quote Display | Shows the text to type | `<p>` with `id="quote"` |
| Message Area | Displays status and success messages | `<p>` with `id="message"` |
| Text Input | Where players type the quote | `<input>` with `id="typed-value"` |
| Start Button | Begins the game | `<button>` with `id="start"` |
- Somewhere to display the quote for the user to type
- Somewhere to display any messages, like a success message
- A textbox for typing
- A start button
**Understanding the UI structure:**
- **Organizes** content logically from top to bottom
- **Assigns** unique IDs to elements for JavaScript targeting
- **Provides** clear visual hierarchy for better user experience
- **Includes** semantic HTML elements for accessibility
Each of those will need IDs so we can work with them in our JavaScript. We will also add references to the CSS and JavaScript files we're going to create.
Create a new file named **index.html**. Add the following HTML:
Create a new file named `index.html`. Add the following HTML:
```html
<!-- inside index.html -->
@ -96,26 +163,52 @@ Create a new file named **index.html**. Add the following HTML:
</html>
```
**Breaking down what this HTML structure accomplishes:**
- **Links** the CSS stylesheet in the `<head>` for styling
- **Creates** a clear heading and instructions for users
- **Establishes** placeholder paragraphs with specific IDs for dynamic content
- **Includes** an input field with accessibility attributes
- **Provides** a start button to trigger the game
- **Loads** the JavaScript file at the end for optimal performance
### Launch the application
Testing your application frequently during development helps you catch issues early and see your progress in real-time. Live Server is an invaluable tool that automatically refreshes your browser whenever you save changes, making development much more efficient.
It's always best to develop iteratively to see how things look. Let's launch our application. There's a wonderful extension for Visual Studio Code called [Live Server](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer&WT.mc_id=academic-77807-sagibbon) which will both host your application locally and refresh the browser each time you save.
- Install [Live Server](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer&WT.mc_id=academic-77807-sagibbon) by following the link and clicking **Install**
- You will be prompted by the browser to open Visual Studio Code, and then by Visual Studio Code to perform the installation
- Restart Visual Studio Code if prompted
- Once installed, in Visual Studio Code, click Ctrl-Shift-P (or Cmd-Shift-P) to open the command palette
- Type **Live Server: Open with Live Server**
- Live Server will start hosting your application
- Open a browser and navigate to **https://localhost:5500**
- You should now see the page you created!
**Install [Live Server](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer&WT.mc_id=academic-77807-sagibbon) by following the link and clicking Install:**
**Here's what happens during installation:**
- **Prompts** your browser to open Visual Studio Code
- **Guides** you through the extension installation process
- **May require** restarting Visual Studio Code to complete setup
**Once installed, in Visual Studio Code, click Ctrl-Shift-P (or Cmd-Shift-P) to open the command palette:**
**Understanding the command palette:**
- **Provides** quick access to all VS Code commands
- **Searches** commands as you type
- **Offers** keyboard shortcuts for faster development
**Type "Live Server: Open with Live Server":**
**What Live Server does:**
- **Starts** a local development server for your project
- **Automatically** refreshes the browser when you save files
- **Serves** your files from a local URL (typically `localhost:5500`)
**Open a browser and navigate to `https://localhost:5500`:**
Let's add some functionality.
You should now see the page you created! Let's add some functionality.
## Add the CSS
With our HTML created, let's add the CSS for core styling. We need to highlight the word the player should be typing, and colorize the textbox if what they've typed is incorrect. We'll do this with two classes.
Now let's make things look good! Visual feedback has been crucial for user interfaces since the early days of computing. In the 1980s, researchers discovered that immediate visual feedback dramatically improves user performance and reduces errors. That's exactly what we're going to create.
Create a new file named **style.css** and add the following syntax.
Our game needs to be crystal clear about what's happening. Players should immediately know which word they're supposed to type, and if they make a mistake, they should see it right away. Let's create some simple but effective styling:
Create a new file named `style.css` and add the following syntax.
```css
/* inside style.css */
@ -129,6 +222,12 @@ Create a new file named **style.css** and add the following syntax.
}
```
**Understanding these CSS classes:**
- **Highlights** the current word with a yellow background for clear visual guidance
- **Signals** typing errors with a light coral background color
- **Provides** immediate feedback without disrupting the user's typing flow
- **Uses** contrasting colors for accessibility and clear visual communication
✅ When it comes to CSS you can layout your page however you might like. Take a little time and make the page look more appealing:
- Choose a different font
@ -137,28 +236,43 @@ Create a new file named **style.css** and add the following syntax.
## JavaScript
With our UI created, it's time to focus our attention on the JavaScript which will provide the logic. We're going to break this down into a handful of steps:
Here's where things get interesting! 🎉 We've got our HTML structure and our CSS styling, but right now our game is like a beautiful car without an engine. JavaScript is going to be that engine - it's what makes everything actually work and respond to what players do.
This is where you'll see your creation come to life. We're going to tackle this step by step so nothing feels overwhelming:
- [Create the constants](#add-the-constants)
- [Event listener to start the game](#add-start-logic)
- [Event listener to typing](#add-typing-logic)
| Step | Purpose | What You'll Learn |
|------|---------|------------------|
| [Create the constants](#add-the-constants) | Set up quotes and DOM references | Variable management and DOM selection |
| [Event listener to start the game](#add-start-logic) | Handle game initialization | Event handling and UI updates |
| [Event listener to typing](#add-typing-logic) | Process user input in real-time | Input validation and dynamic feedback |
But first, create a new file named **script.js**.
**This structured approach helps you:**
- **Organize** your code into logical, manageable sections
- **Build** functionality incrementally for easier debugging
- **Understand** how different parts of your application work together
- **Create** reusable patterns for future projects
But first, create a new file named `script.js`.
### Add the constants
We're going to need a few items to make our lives a little easier for programming. Again, similar to a recipe, here's what we'll need:
Before we dive into the action, let's gather all our resources! Just like how NASA mission control sets up all their monitoring systems before launch, it's much easier when you have everything prepared and ready to go. This saves us from hunting around for things later and helps prevent typos.
- Array with the list of all quotes
- Empty array to store all the words for the current quote
- Space to store the index of the word the player is currently typing
- The time the player clicked start
Here's what we need to set up first:
We're also going to want references to the UI elements:
| Data Type | Purpose | Example |
|-----------|---------|--------|
| Array of quotes | Store all possible quotes for the game | `['Quote 1', 'Quote 2', ...]` |
| Word array | Break current quote into individual words | `['When', 'you', 'have', ...]` |
| Word index | Track which word player is typing | `0, 1, 2, 3...` |
| Start time | Calculate elapsed time for scoring | `Date.now()` |
- The textbox (**typed-value**)
- The quote display (**quote**)
- The message (**message**)
**We'll also need references to our UI elements:**
| Element | ID | Purpose |
|---------|----|---------|
| Text input | `typed-value` | Where players type |
| Quote display | `quote` | Shows the quote to type |
| Message area | `message` | Displays status updates |
```javascript
// inside script.js
@ -183,9 +297,22 @@ const messageElement = document.getElementById('message');
const typedValueElement = document.getElementById('typed-value');
```
**Breaking down what this setup code accomplishes:**
- **Stores** an array of Sherlock Holmes quotes using `const` since the quotes won't change
- **Initializes** tracking variables with `let` since these values will update during gameplay
- **Captures** references to DOM elements using `document.getElementById()` for efficient access
- **Sets up** the foundation for all game functionality with clear, descriptive variable names
- **Organizes** related data and elements logically for easier code maintenance
✅ Go ahead and add more quotes to your game
> **NOTE:** We can retrieve the elements whenever we want in code by using `document.getElementById`. Because of the fact we're going to refer to these elements on a regular basis we're going to avoid typos with string literals by using constants. Frameworks such as [Vue.js](https://vuejs.org/) or [React](https://reactjs.org/) can help you better manage centralizing your code.
> 💡 **Pro Tip**: We can retrieve the elements whenever we want in code by using `document.getElementById()`. Because of the fact we're going to refer to these elements on a regular basis we're going to avoid typos with string literals by using constants. Frameworks such as [Vue.js](https://vuejs.org/) or [React](https://reactjs.org/) can help you better manage centralizing your code.
>
**Here's why this approach works so well:**
- **Prevents** spelling errors when referencing elements multiple times
- **Improves** code readability with descriptive constant names
- **Enables** better IDE support with autocomplete and error checking
- **Makes** refactoring easier if element IDs change later
Take a minute to watch a video on using `const`, `let` and `var`
@ -195,9 +322,11 @@ Take a minute to watch a video on using `const`, `let` and `var`
### Add start logic
To begin the game, the player will click on start. Of course, we don't know when they're going to click start. This is where an [event listener](https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener) comes into play. An event listener will allow us to listen for something to occur (an event) and execute code in response. In our case, we want to execute code when the user clicks on start.
This is where everything clicks into place! 🚀 You're about to write your first real event listener, and there's something quite satisfying about seeing your code respond to a button click.
Think about it: somewhere out there, a player is going to click that "Start" button, and your code needs to be ready for them. We have no idea when they'll click it - could be immediately, could be after they grab a coffee - but when they do, your game springs to life.
When the user clicks **start**, we need to select a quote, setup the user interface, and setup tracking for the current word and timing. Below is the JavaScript you'll need to add; we discuss it just after the script block.
When the user clicks `start`, we need to select a quote, setup the user interface, and setup tracking for the current word and timing. Below is the JavaScript you'll need to add; we discuss it just after the script block.
```javascript
// at the end of script.js
@ -232,27 +361,35 @@ document.getElementById('start').addEventListener('click', () => {
});
```
Let's break down the code!
- Setup the word tracking
- Using [Math.floor](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Math/floor) and [Math.random](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Math/random) allows us to randomly select a quote from the `quotes` array
- We convert the `quote` into an array of `words` so we can track the word the player is currently typing
- `wordIndex` is set to 0, since the player will start on the first word
- Setup the UI
- Create an array of `spanWords`, which contains each word inside a `span` element
- This will allow us to highlight the word on the display
- `join` the array to create a string which we can use to update the `innerHTML` on `quoteElement`
- This will display the quote to the player
- Set the `className` of the first `span` element to `highlight` to highlight it as yellow
- Clean the `messageElement` by setting `innerText` to `''`
- Setup the textbox
- Clear the current `value` on `typedValueElement`
- Set the `focus` to `typedValueElement`
- Start the timer by calling `getTime`
**Let's break down the code into logical sections:**
**📊 Word Tracking Setup:**
- **Selects** a random quote using `Math.floor()` and `Math.random()` for variety
- **Converts** the quote into an array of individual words using `split(' ')`
- **Resets** the `wordIndex` to 0 since players start with the first word
- **Prepares** the game state for a fresh round
**🎨 UI Setup and Display:**
- **Creates** an array of `<span>` elements, wrapping each word for individual styling
- **Joins** the span elements into a single string for efficient DOM updating
- **Highlights** the first word by adding the `highlight` CSS class
- **Clears** any previous game messages to provide a clean slate
**⌨️ Textbox Preparation:**
- **Clears** any existing text in the input field
- **Sets focus** to the textbox so players can start typing immediately
- **Prepares** the input area for the new game session
**⏱️ Timer Initialization:**
- **Captures** the current timestamp using `new Date().getTime()`
- **Enables** accurate calculation of typing speed and completion time
- **Starts** the performance tracking for the game session
### Add typing logic
As the player types, an `input` event will be raised. This event listener will check to ensure the player is typing the word correctly, and handle the current status of the game. Returning to **script.js**, add the following code to the end. We will break it down afterwards.
Here's where we tackle the heart of our game! Don't worry if this seems like a lot at first - we'll walk through every piece, and by the end, you'll see how logical it all is.
What we're building here is quite sophisticated: every single time someone types a letter, our code is going to check what they typed, give them feedback, and decide what should happen next. It's similar to how early word processors like WordStar in the 1970s provided real-time feedback to typists.
```javascript
// at the end of script.js
@ -291,30 +428,68 @@ typedValueElement.addEventListener('input', () => {
});
```
Let's break down the code! We start by grabbing the current word and the value the player has typed thus far. Then we have waterfall logic, where we check if the quote is complete, the word is complete, the word is correct, or (finally), if there is an error.
- Quote is complete, indicated by `typedValue` being equal to `currentWord`, and `wordIndex` being equal to one less than the `length` of `words`
- Calculate `elapsedTime` by subtracting `startTime` from the current time
- Divide `elapsedTime` by 1,000 to convert from milliseconds to seconds
- Display a success message
- Word is complete, indicated by `typedValue` ending with a space (the end of a word) and `typedValue` being equal to `currentWord`
- Set `value` on `typedElement` to be `''` to allow for the next word to be typed
- Increment `wordIndex` to move to the next word
- Loop through all `childNodes` of `quoteElement` to set `className` to `''` to revert to default display
- Set `className` of the current word to `highlight` to flag it as the next word to type
- Word is currently typed correctly (but not complete), indicated by `currentWord` started with `typedValue`
- Ensure `typedValueElement` is displayed as default by clearing `className`
- If we made it this far, we have an error
- Set `className` on `typedValueElement` to `error`
**Understanding the typing logic flow:**
This function uses a waterfall approach, checking conditions from most specific to most general. Let's break down each scenario:
```mermaid
flowchart TD
A[Player types character] --> B[Get current word and typed value]
B --> C{Quote complete?}
C -->|Yes| D[Show completion message with time]
C -->|No| E{Word complete with space?}
E -->|Yes| F[Clear input, move to next word, update highlight]
E -->|No| G{Typing correctly so far?}
G -->|Yes| H[Remove error styling]
G -->|No| I[Show error styling]
```
**🏁 Quote Complete (Scenario 1):**
- **Checks** if typed value matches current word AND we're on the last word
- **Calculates** elapsed time by subtracting start time from current time
- **Converts** milliseconds to seconds by dividing by 1,000
- **Displays** congratulatory message with completion time
**✅ Word Complete (Scenario 2):**
- **Detects** word completion when input ends with a space
- **Validates** that trimmed input matches the current word exactly
- **Clears** the input field for the next word
- **Advances** to the next word by incrementing `wordIndex`
- **Updates** visual highlighting by removing all classes and highlighting the new word
**📝 Typing in Progress (Scenario 3):**
- **Verifies** that the current word starts with what's been typed so far
- **Removes** any error styling to show the input is correct
- **Allows** continued typing without interruption
**❌ Error State (Scenario 4):**
- **Triggers** when typed text doesn't match the expected word beginning
- **Applies** error CSS class to provide immediate visual feedback
- **Helps** players quickly identify and correct mistakes
## Test your application
You've made it to the end! The last step is to ensure our application works. Give it a shot! Don't worry if there are errors; **all developers** have errors. Examine the messages and debug as needed.
Look what you've accomplished! 🎉 You just built a real, working typing game from scratch using event-driven programming. Take a moment to appreciate that - this is no small feat!
Now comes the testing phase! Will it work as expected? Did we miss something? Here's the thing: if something doesn't work perfectly right away, that's completely normal. Even experienced developers find bugs in their code regularly. It's all part of the development process!
Click on **start**, and start typing away! It should look a little like the animation we saw before.
Click on `start`, and start typing away! It should look a little like the animation we saw before.
![Animation of the game in action](../images/demo.gif)
**What to test in your application:**
- **Verifies** that clicking Start displays a random quote
- **Confirms** that typing highlights the current word correctly
- **Checks** that error styling appears for incorrect typing
- **Ensures** that completing words advances the highlight properly
- **Tests** that finishing the quote shows the completion message with timing
**Common debugging tips:**
- **Check** the browser console (F12) for JavaScript errors
- **Verify** that all file names match exactly (case-sensitive)
- **Ensure** Live Server is running and refreshing properly
- **Test** different quotes to verify the random selection works
---
## GitHub Copilot Agent Challenge 🎮
@ -332,14 +507,26 @@ Use the Agent mode to complete the following challenge:
Add the necessary HTML elements, CSS styles, and JavaScript functions to implement this feature. Include proper error handling and ensure the game remains accessible with appropriate ARIA labels.
Learn more about [agent mode](https://code.visualstudio.com/blogs/2025/02/24/introducing-copilot-agent-mode) here.
## 🚀 Challenge
Add more functionality
Ready to take your typing game to the next level? Try implementing these advanced features to deepen your understanding of event handling and DOM manipulation:
**Add more functionality:**
| Feature | Description | Skills You'll Practice |
|---------|-------------|------------------------|
| **Input Control** | Disable the `input` event listener on completion, and re-enable it when the button is clicked | Event management and state control |
| **UI State Management** | Disable the textbox when the player completes the quote | DOM property manipulation |
| **Modal Dialog** | Display a modal dialog box with the success message | Advanced UI patterns and accessibility |
| **High Score System** | Store high scores using `localStorage` | Browser storage APIs and data persistence |
- Disable the `input` event listener on completion, and re-enable it when the button is clicked
- Disable the textbox when the player completes the quote
- Display a modal dialog box with the success message
- Store high scores using [localStorage](https://developer.mozilla.org/docs/Web/API/Window/localStorage)
**Implementation tips:**
- **Research** `localStorage.setItem()` and `localStorage.getItem()` for persistent storage
- **Practice** adding and removing event listeners dynamically
- **Explore** HTML dialog elements or CSS modal patterns
- **Consider** accessibility when disabling and enabling form controls
## Post-Lecture Quiz

@ -2,11 +2,40 @@
## Instructions
Create a small game that uses keyboard events to do tasks. It may be a different kind of typing game, or an art type game that paints pixels to the screen on keystrokes. Get creative!
Now that you've mastered the fundamentals of event-driven programming with the typing game, it's time to unleash your creativity! You'll design and build your own keyboard-based game that demonstrates your understanding of event handling, DOM manipulation, and user interaction patterns.
Create a small game that uses keyboard events to accomplish specific tasks. This could be a different kind of typing game, an art application that paints pixels to the screen on keystrokes, a simple arcade-style game controlled with arrow keys, or any other creative concept you can imagine. Get creative and think about how different keys can trigger different behaviors!
**Your game should include:**
| Requirement | Description | Purpose |
|-------------|-------------|---------|
| **Event Listeners** | Respond to at least 3 different keyboard events | Demonstrate understanding of event handling |
| **Visual Feedback** | Provide immediate visual response to user input | Show mastery of DOM manipulation |
| **Game Logic** | Include scoring, levels, or progression mechanics | Practice implementing application state |
| **User Interface** | Clear instructions and intuitive controls | Develop user experience design skills |
**Creative project ideas to consider:**
- **Rhythm Game**: Players press keys in time with music or visual cues
- **Pixel Art Creator**: Different keys paint different colors or patterns
- **Word Builder**: Players create words by typing letters in specific orders
- **Snake Game**: Control a snake with arrow keys to collect items
- **Music Synthesizer**: Different keys play different musical notes or sounds
- **Speed Typing Variants**: Category-specific typing (programming terms, foreign languages)
- **Keyboard Drummer**: Create beats by mapping keys to different drum sounds
**Implementation guidelines:**
- **Start** with a simple concept and build complexity gradually
- **Focus** on smooth, responsive controls that feel natural
- **Include** clear visual indicators for game state and player progress
- **Test** your game with different users to ensure intuitive gameplay
- **Document** your code with comments explaining your event handling strategy
## Rubric
| Criteria | Exemplary | Adequate | Needs Improvement |
| -------- | ------------------------ | ------------------------ | ----------------- |
| | A full game is presented | The game is very minimal | The game has bugs |
| | | | |
| Criteria | Exemplary | Adequate | Needs Improvement |
| -------- | --------- | -------- | ----------------- |
| **Functionality** | A complete, polished game with multiple features and smooth gameplay | A working game with basic features that demonstrates keyboard event handling | A minimal implementation with limited functionality or significant bugs |
| **Code Quality** | Well-organized, commented code following best practices with efficient event handling | Clean, readable code with appropriate use of event listeners and DOM manipulation | Basic code structure with some organization issues or inefficient implementations |
| **User Experience** | Intuitive controls, clear feedback, and engaging gameplay that feels professional | Functional interface with adequate user guidance and responsive controls | Basic interface with unclear instructions or poor responsiveness |
| **Creativity** | Original concept with innovative use of keyboard events and creative problem-solving | Interesting variation on common game patterns with good use of event handling | Simple implementation of a basic concept with minimal creative elements |

@ -9,140 +9,284 @@
### Introduction
Browser extensions add additional functionality to a browser. But before you build one, you should learn a little about how browsers do their work.
Browser extensions are mini-applications that enhance your web browsing experience. Like Tim Berners-Lee's original vision of an interactive web, extensions extend the browser's capabilities beyond simple document viewing. From password managers that keep your accounts secure to color pickers that help designers grab perfect shades, extensions solve everyday browsing challenges.
### About the browser
Before we build your first extension, let's understand how browsers work. Just as Alexander Graham Bell needed to understand sound transmission before inventing the telephone, knowing browser fundamentals will help you create extensions that integrate seamlessly with existing browser systems.
In this series of lessons, you'll learn how to build a browser extension that will work on Chrome, Firefox and Edge browsers. In this part, you'll discover how browsers work and scaffold out the elements of the browser extension.
By the end of this lesson, you'll understand browser architecture and have started building your first extension.
But what is a browser exactly? It is a software application that allows an end user to access content from a server and display it on web pages.
## Understanding Web Browsers
✅ A little history: the first browser was called 'WorldWideWeb' and was created by Sir Timothy Berners-Lee in 1990.
A web browser is essentially a sophisticated document interpreter. When you type "google.com" into the address bar, the browser performs a complex series of operations - requesting content from servers worldwide, then parsing and rendering that code into the interactive web pages you see.
This process mirrors how the first web browser, WorldWideWeb, was designed by Tim Berners-Lee in 1990 to make hyperlinked documents accessible to everyone.
**A little history**: The first browser was called 'WorldWideWeb' and was created by Sir Timothy Berners-Lee in 1990.
![early browsers](images/earlybrowsers.jpg)
> Some early browsers, via [Karen McGrane](https://www.slideshare.net/KMcGrane/week-4-ixd-history-personal-computing)
When a user connected to the internet using a URL (Uniform Resource Locator) address, usually using Hypertext Transfer Protocol via an `http` or `https` address, the browser communicates with a web server and fetches a web page.
### How Browsers Process Web Content
The process between entering a URL and seeing a webpage involves several coordinated steps that happen within seconds:
```mermaid
sequenceDiagram
participant User
participant Browser
participant DNS
participant Server
User->>Browser: Types URL and presses Enter
Browser->>DNS: Looks up server IP address
DNS->>Browser: Returns IP address
Browser->>Server: Requests web page content
Server->>Browser: Sends HTML, CSS, and JavaScript
Browser->>User: Renders complete web page
```
**Here's what this process accomplishes:**
- **Translates** the human-readable URL into a server IP address through DNS lookup
- **Establishes** a secure connection with the web server using HTTP or HTTPS protocols
- **Requests** the specific web page content from the server
- **Receives** HTML markup, CSS styling, and JavaScript code from the server
- **Renders** all content into the interactive web page you see
### Browser Core Features
At this point, the browser's rendering engine displays it on the user's device, which might be a mobile phone, desktop, or laptop.
Modern browsers provide numerous features that extension developers can leverage:
Browsers also have the ability to cache content so that it doesn't have to be retrieved from the server every time. They can record the history of a user's browsing activity, store 'cookies', which are small bits of data that contain information used to store a user's activity, and more.
| Feature | Purpose | Extension Opportunities |
|---------|---------|------------------------|
| **Rendering Engine** | Displays HTML, CSS, and JavaScript | Content modification, styling injection |
| **JavaScript Engine** | Executes JavaScript code | Custom scripts, API interactions |
| **Local Storage** | Saves data locally | User preferences, cached data |
| **Network Stack** | Handles web requests | Request monitoring, data analysis |
| **Security Model** | Protects users from malicious content | Content filtering, security enhancements |
A really important thing to remember about browsers is that they are not all the same! Each browser has its strengths and weaknesses, and a professional web developer needs to understand how to make web pages perform well cross-browser. This includes handling small viewports such as a mobile phone's, as well as a user who is offline.
**Understanding these features helps you:**
- **Identify** where your extension can add the most value
- **Choose** the right browser APIs for your extension's functionality
- **Design** extensions that work efficiently with browser systems
- **Ensure** your extension follows browser security best practices
A really useful website that you probably should bookmark in whatever browser you prefer to use is [caniuse.com](https://www.caniuse.com). When you are building web pages, it's very helpful to use caniuse's lists of supported technologies so that you can best support your users.
### Cross-Browser Development Considerations
✅ How can you tell what browsers are most popular with your web site's user base? Check your analytics - you can install various analytics packages as part of your web development process, and they will tell you what browsers are most used by the various popular browsers.
Different browsers implement standards with slight variations, similar to how different programming languages might handle the same algorithm differently. Chrome, Firefox, and Safari each have unique characteristics that developers must consider during extension development.
## Browser extensions
> 💡 **Pro Tip**: Use [caniuse.com](https://www.caniuse.com) to check which web technologies are supported across different browsers. This is invaluable when planning your extension's features!
Why would you want to build a browser extension? It's a handy thing to attach to your browser when you need quick access to tasks that you tend to repeat. For example, if you find yourself needing to check colors on the various web pages that you interact with, you might install a color-picker browser extension. If you have trouble remembering passwords, you might use a password-management browser extension.
**Key considerations for extension development:**
- **Test** your extension across Chrome, Firefox, and Edge browsers
- **Adapt** to different browser extension APIs and manifest formats
- **Handle** varying performance characteristics and limitations
- **Provide** fallbacks for browser-specific features that may not be available
Browser extensions are fun to develop, too. They tend to manage a finite number of tasks that they perform well.
**Analytics Insight**: You can determine which browsers your users prefer by installing analytics packages in your web development projects. This data helps you prioritize which browsers to support first.
✅ What are your favorite browser extensions? What tasks do they perform?
## Understanding Browser Extensions
### Installing extensions
Browser extensions solve common web browsing challenges by adding functionality directly to the browser interface. Rather than requiring separate applications or complex workflows, extensions provide immediate access to tools and features.
Before you start building, take a look at the process of building and deploying a browser extension. While each browser varies a bit in how they manage this task, the process is similar on Chrome and Firefox to this example on Edge:
This concept mirrors how early computer pioneers like Douglas Engelbart envisioned augmenting human capabilities with technology - extensions augment your browser's basic functionality.
**Popular extension categories and their benefits:**
- **Productivity Tools**: Task managers, note-taking apps, and time trackers that help you stay organized
- **Security Enhancements**: Password managers, ad blockers, and privacy tools that protect your data
- **Developer Tools**: Code formatters, color pickers, and debugging utilities that streamline development
- **Content Enhancement**: Reading modes, video downloaders, and screenshot tools that improve your web experience
**Reflection Question**: What are your favorite browser extensions? What specific tasks do they perform, and how do they improve your browsing experience?
## Installing and Managing Extensions
Understanding the extension installation process helps you anticipate the user experience when people install your extension. The installation process is standardized across modern browsers, with minor variations in interface design.
![screenshot of the Edge browser showing the open edge://extensions page and open settings menu](images/install-on-edge.png)
> Note: Make sure to toggle on developer mode and allow extension from other stores.
> **Important**: Make sure to toggle on developer mode and allow extensions from other stores when testing your own extensions.
In essence, the process will be:
### Development Extension Installation Process
- build your extension using `npm run build`
- navigate in the browser to the extensions pane using the "Settings and more" button (the `...` icon) on the top right
- if it's a new installation, choose `load unpacked` to upload a fresh extension from its build folder (in our case it is `/dist`)
- or, click `reload` if you are reloading the already-installed extension
When you're developing and testing your own extensions, follow this workflow:
✅ These instructions pertain to extensions you build yourself; to install extensions that have been released to the browser extension store associated to each browser, you should navigate to those [stores](https://microsoftedge.microsoft.com/addons/Microsoft-Edge-Extensions-Home) and install the extension of your choice.
```bash
# Step 1: Build your extension
npm run build
```
### Get Started
**What this command accomplishes:**
- **Compiles** your source code into browser-ready files
- **Bundles** JavaScript modules into optimized packages
- **Generates** the final extension files in the `/dist` folder
- **Prepares** your extension for installation and testing
You're going to build a browser extension that displays your region's carbon footprint, showing your region's energy usage and the source of the energy. The extension will have a form that collects an API key so that you can access
CO2 Signal's API.
**Step 2: Navigate to Browser Extensions**
1. **Open** your browser's extensions management page
2. **Click** the "Settings and more" button (the `...` icon) on the top right
3. **Select** "Extensions" from the dropdown menu
**You need:**
**Step 3: Load Your Extension**
- **For new installations**: Choose `load unpacked` and select your `/dist` folder
- **For updates**: Click `reload` next to your already-installed extension
- **For testing**: Enable "Developer mode" to access additional debugging features
- [an API key](https://www.co2signal.com/); enter your email in the box on this page and one will be sent to you
- the [code for your region](http://api.electricitymap.org/v3/zones) corresponding to the [Electricity Map](https://www.electricitymap.org/map) (in Boston, for example, I use 'US-NEISO').
- the [starter code](../start). Download the `start` folder; you will be completing code in this folder.
- [NPM](https://www.npmjs.com) - NPM is a package management tool; install it locally and the packages listed in your `package.json` file will be installed for use by your web asset
### Production Extension Installation
✅ Learn more about package management in this [excellent Learn module](https://docs.microsoft.com/learn/modules/create-nodejs-project-dependencies/?WT.mc_id=academic-77807-sagibbon)
> ✅ **Note**: These development instructions are specifically for extensions you build yourself. To install published extensions, visit the official browser extension stores like the [Microsoft Edge Add-ons store](https://microsoftedge.microsoft.com/addons/Microsoft-Edge-Extensions-Home).
Take a minute to look through the codebase:
**Understanding the difference:**
- **Development installations** let you test unpublished extensions during development
- **Store installations** provide vetted, published extensions with automatic updates
- **Sideloading** allows installation of extensions from outside official stores (requires developer mode)
dist
-|manifest.json (defaults set here)
-|index.html (front-end HTML markup here)
-|background.js (background JS here)
-|main.js (built JS)
src
-|index.js (your JS code goes here)
## Building Your Carbon Footprint Extension
✅ Once you have your API key and Region code handy, store those somewhere in a note for future use.
We'll create a browser extension that displays the carbon footprint of your region's energy use. This project demonstrates essential extension development concepts while creating a practical tool for environmental awareness.
### Build the HTML for the extension
This approach follows the principle of "learning by doing" that has proven effective since John Dewey's educational theories - combining technical skills with meaningful real-world applications.
This extension has two views. One to gather the API key and region code:
### Project Requirements
![screenshot of the completed extension open in a browser, displaying a form with inputs for region name and API key.](images/1.png)
Before beginning development, let's gather the required resources and dependencies:
**Required API Access:**
- **[CO2 Signal API key](https://www.co2signal.com/)**: Enter your email address to receive your free API key
- **[Region code](http://api.electricitymap.org/v3/zones)**: Find your region code using the [Electricity Map](https://www.electricitymap.org/map) (for example, Boston uses 'US-NEISO')
**Development Tools:**
- **[Node.js and NPM](https://www.npmjs.com)**: Package management tool for installing project dependencies
- **[Starter code](../start)**: Download the `start` folder to begin development
**Learn More**: Enhance your package management skills with this [comprehensive Learn module](https://docs.microsoft.com/learn/modules/create-nodejs-project-dependencies/?WT.mc_id=academic-77807-sagibbon)
### Understanding the Project Structure
Understanding the project structure helps organize development work efficiently. Like how the Library of Alexandria was organized for easy knowledge retrieval, a well-structured codebase makes development more efficient:
```
project-root/
├── dist/ # Built extension files
│ ├── manifest.json # Extension configuration
│ ├── index.html # User interface markup
│ ├── background.js # Background script functionality
│ └── main.js # Compiled JavaScript bundle
└── src/ # Source development files
└── index.js # Your main JavaScript code
```
And the second to display the region's carbon usage:
**Breaking down what each file accomplishes:**
- **`manifest.json`**: **Defines** extension metadata, permissions, and entry points
- **`index.html`**: **Creates** the user interface that appears when users click your extension
- **`background.js`**: **Handles** background tasks and browser event listeners
- **`main.js`**: **Contains** the final bundled JavaScript after the build process
- **`src/index.js`**: **Houses** your main development code that gets compiled into `main.js`
> 💡 **Organization Tip**: Store your API key and region code in a secure note for easy reference during development. You'll need these values to test your extension's functionality.
**Security Note**: Never commit API keys or sensitive credentials to your code repository. We'll show you how to handle these securely in the next steps.
## Creating the Extension Interface
Now we'll build the user interface components. The extension uses a two-screen approach: a configuration screen for initial setup and a results screen for data display.
This follows the progressive disclosure principle used in interface design since the early days of computing - revealing information and options in a logical sequence to avoid overwhelming users.
### Extension Views Overview
**Setup View** - First-time user configuration:
![screenshot of the completed extension open in a browser, displaying a form with inputs for region name and API key.](images/1.png)
**Results View** - Carbon footprint data display:
![screenshot of the completed extension displaying values for carbon usage and fossil fuel percentage for the US-NEISO region.](images/2.png)
Let's start by building the HTML for the form and styling it with CSS.
### Building the Configuration Form
In the `/dist` folder, you will build a form and a result area. In the `index.html` file, populate the delineated form area:
The setup form collects user configuration data during initial use. Once configured, this information persists in browser storage for future sessions.
```HTML
In the `/dist/index.html` file, add this form structure:
```html
<form class="form-data" autocomplete="on">
<div>
<h2>New? Add your Information</h2>
</div>
<div>
<label for="region">Region Name</label>
<input type="text" id="region" required class="region-name" />
</div>
<div>
<label for="api">Your API Key from tmrow</label>
<input type="text" id="api" required class="api-key" />
</div>
<button class="search-btn">Submit</button>
</form>
<div>
<h2>New? Add your Information</h2>
</div>
<div>
<label for="region">Region Name</label>
<input type="text" id="region" required class="region-name" />
</div>
<div>
<label for="api">Your API Key from tmrow</label>
<input type="text" id="api" required class="api-key" />
</div>
<button class="search-btn">Submit</button>
</form>
```
This is the form where your saved information will be input and saved to local storage.
Next, create the results area; under the final form tag, add some divs:
**Here's what this form accomplishes:**
- **Creates** a semantic form structure with proper labels and input associations
- **Enables** browser autocomplete functionality for improved user experience
- **Requires** both fields to be filled before submission using the `required` attribute
- **Organizes** inputs with descriptive class names for easy styling and JavaScript targeting
- **Provides** clear instructions for users who are setting up the extension for the first time
### Building the Results Display
```HTML
Next, create the results area that will show the carbon footprint data. Add this HTML below the form:
```html
<div class="result">
<div class="loading">loading...</div>
<div class="errors"></div>
<div class="data"></div>
<div class="result-container">
<p><strong>Region: </strong><span class="my-region"></span></p>
<p><strong>Carbon Usage: </strong><span class="carbon-usage"></span></p>
<p><strong>Fossil Fuel Percentage: </strong><span class="fossil-fuel"></span></p>
</div>
<button class="clear-btn">Change region</button>
<div class="loading">loading...</div>
<div class="errors"></div>
<div class="data"></div>
<div class="result-container">
<p><strong>Region: </strong><span class="my-region"></span></p>
<p><strong>Carbon Usage: </strong><span class="carbon-usage"></span></p>
<p><strong>Fossil Fuel Percentage: </strong><span class="fossil-fuel"></span></p>
</div>
<button class="clear-btn">Change region</button>
</div>
```
At this point, you can try a build. Make sure to install the package dependencies of this extension:
```
**Breaking down what this structure provides:**
- **`loading`**: **Displays** a loading message while API data is being fetched
- **`errors`**: **Shows** error messages if API calls fail or data is invalid
- **`data`**: **Holds** raw data for debugging purposes during development
- **`result-container`**: **Presents** formatted carbon footprint information to users
- **`clear-btn`**: **Allows** users to change their region and reconfigure the extension
### Setting Up the Build Process
Now let's install the project dependencies and test the build process:
```bash
npm install
```
This command will use npm, the Node Package Manager, to install webpack for your extension's build process. Webpack is a bundler that handles compiling code. You can see the output of this process by looking in `/dist/main.js` - you see the code has been bundled.
**What this installation process accomplishes:**
- **Downloads** Webpack and other development dependencies specified in `package.json`
- **Configures** the build toolchain for compiling modern JavaScript
- **Prepares** the development environment for extension building and testing
- **Enables** code bundling, optimization, and cross-browser compatibility features
> 💡 **Build Process Insight**: Webpack bundles your source code from `/src/index.js` into `/dist/main.js`. This process optimizes your code for production and ensures browser compatibility.
### Testing Your Progress
At this point, you can test your extension:
1. **Run** the build command to compile your code
2. **Load** the extension into your browser using the developer mode
3. **Verify** that the form displays correctly and looks professional
4. **Check** that all form elements are properly aligned and functional
For now, the extension should build and, if you deploy it into Edge as an extension, you'll see a form neatly displayed.
**What you've accomplished:**
- **Built** the foundational HTML structure for your extension
- **Created** both configuration and results interfaces with proper semantic markup
- **Set up** a modern development workflow using industry-standard tools
- **Prepared** the foundation for adding interactive JavaScript functionality
Congratulations, you've taken the first steps towards building a browser extension. In subsequent lessons, you'll make it more functional and useful.
You've completed the first phase of browser extension development. Like how the Wright brothers first needed to understand aerodynamics before achieving flight, understanding these foundational concepts prepares you for building more complex interactive features in the next lesson.
## GitHub Copilot Agent Challenge 🚀
@ -152,7 +296,7 @@ Use the Agent mode to complete the following challenge:
**Prompt:** Create JavaScript validation functions that check if the API key field contains at least 20 characters and if the region code follows the correct format (like 'US-NEISO'). Add visual feedback by changing input border colors to green for valid inputs and red for invalid ones. Also add a toggle feature to show/hide the API key for security purposes.
---
Learn more about [agent mode](https://code.visualstudio.com/blogs/2025/02/24/introducing-copilot-agent-mode) here.
## 🚀 Challenge

@ -1,11 +1,130 @@
# Restyle your Extension
# Assignment: Restyle Your Browser Extension
## Overview
Now that you've built the HTML structure for your carbon footprint browser extension, it's time to make it visually appealing and user-friendly. Great design enhances the user experience and makes your extension more professional and engaging.
Your extension comes with basic CSS styling, but this assignment challenges you to create a unique visual identity that reflects your personal style while maintaining excellent usability.
## Instructions
The codebase for this extension comes complete with styles, but you don't have to use them; make your extension your own by restyling it by editing its css file.
### Part 1: Analyze the Current Design
Before making changes, examine the existing CSS structure:
1. **Locate** the CSS files in your extension project
2. **Review** the current styling approach and color scheme
3. **Identify** areas for improvement in layout, typography, and visual hierarchy
4. **Consider** how the design supports user goals (easy form completion and clear data display)
### Part 2: Design Your Custom Styling
Create a cohesive visual design that includes:
**Color Scheme:**
- Choose a primary color palette that reflects environmental themes
- Ensure sufficient contrast for accessibility (use tools like WebAIM's contrast checker)
- Consider how colors will look across different browser themes
**Typography:**
- Select readable fonts that work well at small extension sizes
- Establish a clear hierarchy with appropriate font sizes and weights
- Ensure text remains legible in both light and dark browser themes
**Layout and Spacing:**
- Improve the visual organization of form elements and data display
- Add appropriate padding and margins for better readability
- Consider responsive design principles for different screen sizes
### Part 3: Implement Your Design
Modify the CSS files to implement your design:
```css
/* Example starting points for customization */
.form-data {
/* Style the configuration form */
background: /* your choice */;
padding: /* your spacing */;
border-radius: /* your preference */;
}
.result-container {
/* Style the data display area */
background: /* complementary color */;
border: /* your border style */;
margin: /* your spacing */;
}
/* Add your custom styles here */
```
**Key areas to style:**
- **Form elements**: Input fields, labels, and submit button
- **Results display**: Data container, text styling, and loading states
- **Interactive elements**: Hover effects, button states, and transitions
- **Overall layout**: Container spacing, background colors, and visual hierarchy
### Part 4: Test and Refine
1. **Build** your extension with `npm run build`
2. **Load** the updated extension into your browser
3. **Test** all visual states (form entry, loading, results display, errors)
4. **Verify** accessibility with browser developer tools
5. **Refine** your styles based on actual usage
## Creative Challenges
### Basic Level
- Update colors and fonts to create a cohesive theme
- Improve spacing and alignment throughout the interface
- Add subtle hover effects to interactive elements
### Intermediate Level
- Design custom icons or graphics for your extension
- Implement smooth transitions between different states
- Create a unique loading animation for API calls
### Advanced Level
- Design multiple theme options (light/dark/high-contrast)
- Implement responsive design for different browser window sizes
- Add micro-interactions that enhance the user experience
## Submission Guidelines
Your completed assignment should include:
- **Modified CSS files** with your custom styling
- **Screenshots** showing your extension in different states (form, loading, results)
- **Brief description** (2-3 sentences) explaining your design choices and how they improve the user experience
## Assessment Rubric
| Criteria | Exemplary (4) | Proficient (3) | Developing (2) | Beginning (1) |
|----------|---------------|----------------|----------------|----------------|
| **Visual Design** | Creative, cohesive design that enhances usability and reflects strong design principles | Good design choices with consistent styling and clear visual hierarchy | Basic design improvements with some consistency issues | Minimal styling changes or inconsistent design |
| **Functionality** | All styles work perfectly across different states and browser environments | Styles work well with minor issues in edge cases | Most styles functional with some display problems | Significant styling issues that impact usability |
| **Code Quality** | Clean, well-organized CSS with meaningful class names and efficient selectors | Good CSS structure with appropriate use of selectors and properties | Acceptable CSS with some organization issues | Poor CSS structure or overly complex styling |
| **Accessibility** | Excellent color contrast, readable fonts, and consideration for users with disabilities | Good accessibility practices with minor areas for improvement | Basic accessibility considerations with some issues | Limited attention to accessibility requirements |
## Tips for Success
> 💡 **Design Tip**: Start with subtle changes and build up to more dramatic styling. Small improvements in typography and spacing often have big impacts on perceived quality.
**Best practices to follow:**
- **Test** your extension in both light and dark browser themes
- **Use** relative units (em, rem) for better scalability
- **Maintain** consistent spacing using CSS custom properties
- **Consider** how your design will look to users with different visual needs
- **Validate** your CSS to ensure it follows proper syntax
> ⚠️ **Common Mistake**: Don't sacrifice usability for visual appeal. Your extension should be both beautiful and functional.
## Rubric
**Remember to:**
- **Keep** important information easily readable
- **Ensure** buttons and interactive elements are easy to click
- **Maintain** clear visual feedback for user actions
- **Test** your design with real data, not just placeholder text
| Criteria | Exemplary | Adequate | Needs Improvement |
| -------- | -------------------------------------------- | --------------------- | ----------------- |
| | Code is submitted with functional new styles | Styling is incomplete | Styles are buggy |
Good luck creating a browser extension that's both functional and visually stunning!

@ -4,19 +4,25 @@
[Pre-lecture quiz](https://ff-quizzes.netlify.app/web/quiz/25)
### Introduction
## Introduction
In this lesson, you'll call an API by submitting your browser extension's form and displaying the results in your browser extension. In addition, you'll learn about how you can store data in your browser's local storage for future reference and use.
Remember that browser extension you started building? Right now you've got a nice-looking form, but it's essentially static. Today we'll bring it to life by connecting it to real data and giving it memory.
Think about the Apollo mission control computers - they didn't just display fixed information. They constantly communicated with spacecraft, updated with telemetry data, and remembered critical mission parameters. That's the kind of dynamic behavior we're building today. Your extension will reach out to the internet, grab real environmental data, and remember your settings for next time.
API integration might sound complex, but it's really just teaching your code how to communicate with other services. Whether you're fetching weather data, social media feeds, or carbon footprint information like we'll do today, it's all about establishing these digital connections. We'll also explore how browsers can persist information - similar to how libraries have used card catalogs to remember where books belong.
By the end of this lesson, you'll have a browser extension that fetches real data, stores user preferences, and provides a smooth experience. Let's get started!
✅ Follow the numbered segments in the appropriate files to know where to place your code
### Set up the elements to manipulate in the extension:
## Set up the elements to manipulate in the extension
By this time you have built the HTML for the form and results `<div>` for your browser extension. From now on, you'll need to work in the `/src/index.js` file and build your extension bit by bit. Refer to the [previous lesson](../1-about-browsers/README.md) on getting your project set up and on the build process.
Before your JavaScript can manipulate the interface, it needs references to specific HTML elements. Think of it like a telescope needing to be pointed at particular stars - before Galileo could study Jupiter's moons, he had to locate and focus on Jupiter itself.
Working in your `index.js` file, start by creating some `const` variables to hold the values associated with various fields:
In your `index.js` file, we'll create `const` variables that capture references to each important form element. This is similar to how scientists label their equipment - instead of searching through the entire laboratory each time, they can directly access what they need.
```JavaScript
```javascript
// form fields
const form = document.querySelector('.form-data');
const region = document.querySelector('.region-name');
@ -32,120 +38,175 @@ const myregion = document.querySelector('.my-region');
const clearBtn = document.querySelector('.clear-btn');
```
All of these fields are referenced by their css class, as you set it up in the HTML in the previous lesson.
**Here's what this code does:**
- **Captures** form elements using `document.querySelector()` with CSS class selectors
- **Creates** references to input fields for the region name and API key
- **Establishes** connections to result display elements for carbon usage data
- **Sets up** access to UI elements like loading indicators and error messages
- **Stores** each element reference in a `const` variable for easy reuse throughout your code
### Add listeners
## Add event listeners
Next, add event listeners to the form and the clear button that resets the form, so that if a user submits the form or clicks that reset button, something will happen, and add the call to initialize the app at the bottom of the file:
Now we'll make your extension respond to user actions. Event listeners are your code's way of monitoring user interactions. Think of them like the operators in early telephone exchanges - they listened for incoming calls and connected the right circuits when someone wanted to make a connection.
```JavaScript
```javascript
form.addEventListener('submit', (e) => handleSubmit(e));
clearBtn.addEventListener('click', (e) => reset(e));
init();
```
✅ Notice the shorthand used to listen for a submit or click event, and how the event it is passed to the handleSubmit or reset functions. Can you write the equivalent of this shorthand in a longer format? Which do you prefer?
**Understanding these concepts:**
- **Attaches** a submit listener to the form that triggers when users press Enter or click submit
- **Connects** a click listener to the clear button for resetting the form
- **Passes** the event object `(e)` to handler functions for additional control
- **Calls** the `init()` function immediately to set up the initial state of your extension
### Build out the init() function and the reset() function:
✅ Notice the shorthand arrow function syntax used here. This modern JavaScript approach is cleaner than traditional function expressions, but both work equally well!
Now you are going to build the function that initializes the extension, which is called init():
## Build the initialization and reset functions
```JavaScript
Let's create the initialization logic for your extension. The `init()` function is like a ship's navigation system checking its instruments - it determines the current state and adjusts the interface accordingly. It checks if someone has used your extension before and loads their previous settings.
The `reset()` function provides users with a fresh start - similar to how scientists reset their instruments between experiments to ensure clean data.
```javascript
function init() {
//if anything is in localStorage, pick it up
// Check if user has previously saved API credentials
const storedApiKey = localStorage.getItem('apiKey');
const storedRegion = localStorage.getItem('regionName');
//set icon to be generic green
//todo
// Set extension icon to generic green (placeholder for future lesson)
// TODO: Implement icon update in next lesson
if (storedApiKey === null || storedRegion === null) {
//if we don't have the keys, show the form
// First-time user: show the setup form
form.style.display = 'block';
results.style.display = 'none';
loading.style.display = 'none';
clearBtn.style.display = 'none';
errors.textContent = '';
} else {
//if we have saved keys/regions in localStorage, show results when they load
displayCarbonUsage(storedApiKey, storedRegion);
// Returning user: load their saved data automatically
displayCarbonUsage(storedApiKey, storedRegion);
results.style.display = 'none';
form.style.display = 'none';
clearBtn.style.display = 'block';
}
};
}
function reset(e) {
e.preventDefault();
//clear local storage for region only
// Clear stored region to allow user to choose a new location
localStorage.removeItem('regionName');
// Restart the initialization process
init();
}
```
In this function, there is some interesting logic. Reading through it, can you see what happens?
- two `const` are set up to check if the user has stored an APIKey and region code in local storage.
- if either of those is null, show the form by changing its style to display as 'block'
- hide the results, loading, and clearBtn and set any error text to an empty string
- if there exists a key and region, start a routine to:
- call the API to get carbon usage data
- hide the results area
- hide the form
- show the reset button
**Breaking down what happens here:**
- **Retrieves** stored API key and region from browser's local storage
- **Checks** if this is a first-time user (no stored credentials) or returning user
- **Shows** the setup form for new users and hides other interface elements
- **Loads** saved data automatically for returning users and displays the reset option
- **Manages** the user interface state based on available data
**Key concepts about Local Storage:**
- **Persists** data between browser sessions (unlike session storage)
- **Stores** data as key-value pairs using `getItem()` and `setItem()`
- **Returns** `null` when no data exists for a given key
- **Provides** a simple way to remember user preferences and settings
Before moving on, it's useful to learn about a very important concept available in browsers: [LocalStorage](https://developer.mozilla.org/docs/Web/API/Window/localStorage). LocalStorage is a useful way to store strings in the browser as a `key-value` pair. This type of web storage can be manipulated by JavaScript to manage data in the browser. LocalStorage does not expire, while SessionStorage, another kind of web storage, is cleared when the browser is closed. The various types of storage have pros and cons to their usage.
> 💡 **Understanding Browser Storage**: [LocalStorage](https://developer.mozilla.org/docs/Web/API/Window/localStorage) is like giving your extension persistent memory. Consider how the ancient Library of Alexandria stored scrolls - information remained available even when scholars left and returned.
>
> **Key characteristics:**
> - **Persists** data even after you close your browser
> - **Survives** computer restarts and browser crashes
> - **Provides** substantial storage space for user preferences
> - **Offers** instant access without network delays
> Note - your browser extension has its own local storage; the main browser window is a different instance and behaves separately.
> **Important Note**: Your browser extension has its own isolated local storage that's separate from regular web pages. This provides security and prevents conflicts with other websites.
You set your APIKey to have a string value, for example, and you can see that it is set on Edge by "inspecting" a web page (you can right-click a browser to inspect) and going to the Applications tab to see the storage.
You can view your stored data by opening browser Developer Tools (F12), navigating to the **Application** tab, and expanding the **Local Storage** section.
![Local storage pane](images/localstorage.png)
✅ Think about situations where you would NOT want to store some data in LocalStorage. In general, placing API Keys in LocalStorage is a bad idea! Can you see why? In our case, since our app is purely for learning and will not be deployed to an app store, we will use this method.
> ⚠️ **Security Consideration**: In production applications, storing API keys in LocalStorage poses security risks since JavaScript can access this data. For learning purposes, this approach works fine, but real applications should use secure server-side storage for sensitive credentials.
Notice that you use the Web API to manipulate LocalStorage, either by using `getItem()`, `setItem()`, or `removeItem()`. It's widely supported across browsers.
## Handle form submission
Before building the `displayCarbonUsage()` function that is called in `init()`, let's build the functionality to handle the initial form submission.
Now we'll handle what happens when someone submits your form. By default, browsers reload the page when forms are submitted, but we'll intercept this behavior to create a smoother experience.
### Handle the form submission
This approach mirrors how mission control handles spacecraft communications - instead of resetting the entire system for each transmission, they maintain continuous operation while processing new information.
Create a function called `handleSubmit` that accepts an event argument `(e)`. Stop the event from propagating (in this case, we want to stop the browser from refreshing) and call a new function, `setUpUser`, passing in the arguments `apiKey.value` and `region.value`. In this way, you use the two values that are brought in via the initial form when the appropriate fields are populated.
Create a function that captures the form submission event and extracts the user's input:
```JavaScript
```javascript
function handleSubmit(e) {
e.preventDefault();
setUpUser(apiKey.value, region.value);
}
```
✅ Refresh your memory - the HTML you set up in the last lesson has two input fields whose `values` are captured via the `const` you set up at the top of the file, and they are both `required` so the browser stops users from inputting null values.
### Set up the user
**In the above, we've:**
- **Prevents** the default form submission behavior that would refresh the page
- **Extracts** user input values from the API key and region fields
- **Passes** the form data to the `setUpUser()` function for processing
- **Maintains** single-page application behavior by avoiding page reloads
✅ Remember that your HTML form fields include the `required` attribute, so the browser automatically validates that users provide both the API key and region before this function runs.
Moving on to the `setUpUser` function, here is where you set local storage values for apiKey and regionName. Add a new function:
## Set up user preferences
```JavaScript
The `setUpUser` function is responsible for saving the user's credentials and initiating the first API call. This creates a smooth transition from setup to displaying results.
```javascript
function setUpUser(apiKey, regionName) {
// Save user credentials for future sessions
localStorage.setItem('apiKey', apiKey);
localStorage.setItem('regionName', regionName);
// Update UI to show loading state
loading.style.display = 'block';
errors.textContent = '';
clearBtn.style.display = 'block';
//make initial call
// Fetch carbon usage data with user's credentials
displayCarbonUsage(apiKey, regionName);
}
```
This function sets a loading message to show while the API is called. At this point, you have arrived at creating the most important function of this browser extension!
### Display Carbon Usage
**Step by step, here's what's happening:**
- **Saves** the API key and region name to local storage for future use
- **Shows** a loading indicator to inform users that data is being fetched
- **Clears** any previous error messages from the display
- **Reveals** the clear button for users to reset their settings later
- **Initiates** the API call to fetch real carbon usage data
This function creates a seamless user experience by managing both data persistence and user interface updates in one coordinated action.
## Display carbon usage data
Finally, it's time to query the API!
Now we'll connect your extension to external data sources through APIs. This transforms your extension from a standalone tool into something that can access real-time information from across the internet.
Before going further, we should discuss APIs. APIs, or [Application Programming Interfaces](https://www.webopedia.com/TERM/A/API.html), is a critical element of a web developer's toolbox. They provide standard ways for programs to interact and interface with each other. For example, if you are building a web site that needs to query a database, someone might have created an API for you to use. While there are many types of APIs, one of the most popular is a [REST API](https://www.smashingmagazine.com/2018/01/understanding-using-rest-api/).
**Understanding APIs**
✅ The term 'REST' stands for 'Representational State Transfer' and features using variously-configured URLs to fetch data. Do a little research on the various types of APIs available to developers. What format appeals to you?
[APIs](https://www.webopedia.com/TERM/A/API.html) are how different applications communicate with each other. Think of them like the telegraph system that connected distant cities in the 19th century - operators would send requests to distant stations and receive responses with the requested information. Every time you check social media, ask a voice assistant a question, or use a delivery app, APIs are facilitating these data exchanges.
There are important things to note about this function. First, notice the [`async` keyword](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/async_function). Writing your functions so that they run asynchronously means that they wait for an action, such as data being returned, to be completed before continuing.
**Key concepts about REST APIs:**
- **REST** stands for 'Representational State Transfer'
- **Uses** standard HTTP methods (GET, POST, PUT, DELETE) to interact with data
- **Returns** data in predictable formats, typically JSON
- **Provides** consistent, URL-based endpoints for different types of requests
✅ The [CO2 Signal API](https://www.co2signal.com/) we'll use provides real-time carbon intensity data from electrical grids worldwide. This helps users understand the environmental impact of their electricity usage!
> 💡 **Understanding Asynchronous JavaScript**: The [`async` keyword](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/async_function) enables your code to handle multiple operations simultaneously. When you request data from a server, you don't want your entire extension to freeze - that would be like air traffic control stopping all operations while waiting for one plane to respond.
>
> **Key benefits:**
> - **Maintains** extension responsiveness while data loads
> - **Allows** other code to continue executing during network requests
> - **Improves** code readability compared to traditional callback patterns
> - **Enables** graceful error handling for network issues
Here's a quick video about `async`:
@ -153,56 +214,82 @@ Here's a quick video about `async`:
> 🎥 Click the image above for a video about async/await.
Create a new function to query the C02Signal API:
```JavaScript
import axios from '../node_modules/axios';
Create the function to fetch and display carbon usage data:
```javascript
// Modern fetch API approach (no external dependencies needed)
async function displayCarbonUsage(apiKey, region) {
try {
await axios
.get('https://api.co2signal.com/v1/latest', {
params: {
countryCode: region,
},
headers: {
'auth-token': apiKey,
},
})
.then((response) => {
let CO2 = Math.floor(response.data.data.carbonIntensity);
//calculateColor(CO2);
loading.style.display = 'none';
form.style.display = 'none';
myregion.textContent = region;
usage.textContent =
Math.round(response.data.data.carbonIntensity) + ' grams (grams C02 emitted per kilowatt hour)';
fossilfuel.textContent =
response.data.data.fossilFuelPercentage.toFixed(2) +
'% (percentage of fossil fuels used to generate electricity)';
results.style.display = 'block';
});
// Fetch carbon intensity data from CO2 Signal API
const response = await fetch('https://api.co2signal.com/v1/latest', {
method: 'GET',
headers: {
'auth-token': apiKey,
'Content-Type': 'application/json'
},
// Add query parameters for the specific region
...new URLSearchParams({ countryCode: region }) && {
url: `https://api.co2signal.com/v1/latest?countryCode=${region}`
}
});
// Check if the API request was successful
if (!response.ok) {
throw new Error(`API request failed: ${response.status}`);
}
const data = await response.json();
const carbonData = data.data;
// Calculate rounded carbon intensity value
const carbonIntensity = Math.round(carbonData.carbonIntensity);
// Update the user interface with fetched data
loading.style.display = 'none';
form.style.display = 'none';
myregion.textContent = region.toUpperCase();
usage.textContent = `${carbonIntensity} grams (grams CO₂ emitted per kilowatt hour)`;
fossilfuel.textContent = `${carbonData.fossilFuelPercentage.toFixed(2)}% (percentage of fossil fuels used to generate electricity)`;
results.style.display = 'block';
// TODO: calculateColor(carbonIntensity) - implement in next lesson
} catch (error) {
console.log(error);
console.error('Error fetching carbon data:', error);
// Show user-friendly error message
loading.style.display = 'none';
results.style.display = 'none';
errors.textContent = 'Sorry, we have no data for the region you have requested.';
errors.textContent = 'Sorry, we couldn\'t fetch data for that region. Please check your API key and region code.';
}
}
```
This is a big function. What's going on here?
**Breaking down what happens here:**
- **Uses** the modern `fetch()` API instead of external libraries like Axios for cleaner, dependency-free code
- **Implements** proper error checking with `response.ok` to catch API failures early
- **Handles** asynchronous operations with `async/await` for more readable code flow
- **Authenticates** with the CO2 Signal API using the `auth-token` header
- **Parses** JSON response data and extracts carbon intensity information
- **Updates** multiple UI elements with formatted environmental data
- **Provides** user-friendly error messages when API calls fail
**Key modern JavaScript concepts demonstrated:**
- **Template literals** with `${}` syntax for clean string formatting
- **Error handling** with try/catch blocks for robust applications
- **Async/await** pattern for handling network requests gracefully
- **Object destructuring** to extract specific data from API responses
- **Method chaining** for multiple DOM manipulations
- following best practices, you use an `async` keyword to make this function behave asynchronously. The function contains a `try/catch` block as it will return a promise when the API returns data. Because you don't have control over the speed that the API will respond (it may not respond at all!), you need to handle this uncertainty by calling it asynchronously.
- you're querying the co2signal API to get your region's data, using your API Key. To use that key, you have to use a type of authentication in your header parameters.
- once the API responds, you assign various elements of its response data to the parts of your screen you set up to show this data.
- if there's an error, or if there is no result, you show an error message.
✅ This function demonstrates several important web development concepts - communicating with external servers, handling authentication, processing data, updating interfaces, and managing errors gracefully. These are fundamental skills that professional developers use regularly.
✅ Using asynchronous programming patterns is another very useful tool in your toolbox. Read [about the various ways](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/async_function) you can configure this type of code.
🎉 **What you've accomplished:** You've created a browser extension that:
- **Connects** to the internet and retrieves real environmental data
- **Persists** user settings between sessions
- **Handles** errors gracefully instead of crashing
- **Provides** a smooth, professional user experience
Congratulations! If you build your extension (`npm run build`) and refresh it in your extensions pane, you have a working extension! The only thing that isn't working is the icon, and you'll fix that in the next lesson.
Test your work by running `npm run build` and refreshing your extension in the browser. You now have a functional carbon footprint tracker. The next lesson will add dynamic icon functionality to complete the extension.
---
@ -214,9 +301,25 @@ Use the Agent mode to complete the following challenge:
**Prompt:** Create an enhanced version of the displayCarbonUsage function that includes: 1) A retry mechanism for failed API calls with exponential backoff, 2) Input validation for the region code before making the API call, 3) A loading animation with progress indicators, 4) Caching of API responses in localStorage with expiration timestamps (cache for 30 minutes), and 5) A feature to display historical data from previous API calls. Also add proper TypeScript-style JSDoc comments to document all function parameters and return types.
Learn more about [agent mode](https://code.visualstudio.com/blogs/2025/02/24/introducing-copilot-agent-mode) here.
## 🚀 Challenge
We've discussed several types of API so far in these lessons. Choose a web API and research in depth what it offers. For example, take a look at APIs available within browsers such as the [HTML Drag and Drop API](https://developer.mozilla.org/docs/Web/API/HTML_Drag_and_Drop_API). What makes a great API in your opinion?
Expand your understanding of APIs by exploring the wealth of browser-based APIs available for web development. Choose one of these browser APIs and build a small demonstration:
- [Geolocation API](https://developer.mozilla.org/docs/Web/API/Geolocation_API) - Get user's current location
- [Notification API](https://developer.mozilla.org/docs/Web/API/Notifications_API) - Send desktop notifications
- [HTML Drag and Drop API](https://developer.mozilla.org/docs/Web/API/HTML_Drag_and_Drop_API) - Create interactive drag interfaces
- [Web Storage API](https://developer.mozilla.org/docs/Web/API/Web_Storage_API) - Advanced local storage techniques
- [Fetch API](https://developer.mozilla.org/docs/Web/API/Fetch_API) - Modern alternative to XMLHttpRequest
**Research questions to consider:**
- What real-world problems does this API solve?
- How does the API handle errors and edge cases?
- What security considerations exist when using this API?
- How widely supported is this API across different browsers?
After your research, identify what characteristics make an API developer-friendly and reliable.
## Post-Lecture Quiz

@ -1,11 +1,93 @@
# Adopt an API
## Overview
APIs open up endless possibilities for creative web development! In this assignment, you'll choose an external API and build a browser extension that solves a real problem or provides valuable functionality to users.
## Instructions
APIs can be very fun to play with. Here is a [list of many free ones](https://github.com/public-apis/public-apis). Pick an API, and build a browser extension that solves a problem. It can be as small a problem of not having enough pet pictures (so, try the [dog CEO API](https://dog.ceo/dog-api/)) or something bigger - have fun!
### Step 1: Choose Your API
Select an API from this curated [list of free public APIs](https://github.com/public-apis/public-apis). Consider these categories:
**Popular options for beginners:**
- **Entertainment**: [Dog CEO API](https://dog.ceo/dog-api/) for random dog pictures
- **Weather**: [OpenWeatherMap](https://openweathermap.org/api) for current weather data
- **Quotes**: [Quotable API](https://quotable.io/) for inspirational quotes
- **News**: [NewsAPI](https://newsapi.org/) for current headlines
- **Fun Facts**: [Numbers API](http://numbersapi.com/) for interesting number facts
### Step 2: Plan Your Extension
Before coding, answer these planning questions:
- What problem does your extension solve?
- Who is your target user?
- What data will you store in local storage?
- How will you handle API failures or rate limits?
### Step 3: Build Your Extension
Your extension should include:
**Required Features:**
- Form inputs for any required API parameters
- API integration with proper error handling
- Local storage for user preferences or API keys
- Clean, responsive user interface
- Loading states and user feedback
**Code Requirements:**
- Use modern JavaScript (ES6+) features
- Implement async/await for API calls
- Include proper error handling with try/catch blocks
- Add meaningful comments explaining your code
- Follow consistent code formatting
### Step 4: Test and Polish
- Test your extension with various inputs
- Handle edge cases (no internet, invalid API responses)
- Ensure your extension works after browser restart
- Add user-friendly error messages
## Bonus Challenges
Take your extension to the next level:
- Add multiple API endpoints for richer functionality
- Implement data caching to reduce API calls
- Create keyboard shortcuts for common actions
- Add data export/import features
- Implement user customization options
## Submission Requirements
1. **Working browser extension** that successfully integrates with your chosen API
2. **README file** explaining:
- Which API you chose and why
- How to install and use your extension
- Any API keys or setup required
- Screenshots of your extension in action
3. **Clean, commented code** following modern JavaScript practices
## Rubric
| Criteria | Exemplary | Adequate | Needs Improvement |
| -------- | -------------------------------------------------------------------------- | ---------------------------------------- | ----------------------- |
| | A complete browser extension is submitted using an API from the above list | A partial browser extension is submitted | The submission has bugs |
| Criteria | Exemplary (90-100%) | Proficient (80-89%) | Developing (70-79%) | Beginning (60-69%) |
|----------|---------------------|---------------------|---------------------|--------------------|
| **API Integration** | Flawless API integration with comprehensive error handling and edge case management | Successful API integration with basic error handling | API works but has limited error handling | API integration has significant issues |
| **Code Quality** | Clean, well-commented modern JavaScript following best practices | Good code structure with adequate comments | Code works but needs better organization | Poor code quality with minimal comments |
| **User Experience** | Polished interface with excellent loading states and user feedback | Good interface with basic user feedback | Basic interface that functions adequately | Poor user experience with confusing interface |
| **Local Storage** | Sophisticated use of local storage with data validation and management | Proper implementation of local storage for key features | Basic local storage implementation | Minimal or incorrect use of local storage |
| **Documentation** | Comprehensive README with setup instructions and screenshots | Good documentation covering most requirements | Basic documentation missing some details | Poor or missing documentation |
## Getting Started Tips
1. **Start simple**: Choose an API that doesn't require complex authentication
2. **Read the docs**: Thoroughly understand your chosen API's endpoints and responses
3. **Plan your UI**: Sketch your extension's interface before coding
4. **Test frequently**: Build incrementally and test each feature as you add it
5. **Handle errors**: Always assume API calls might fail and plan accordingly
## Resources
- [Browser Extension Documentation](https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions)
- [Fetch API Guide](https://developer.mozilla.org/docs/Web/API/Fetch_API/Using_Fetch)
- [Local Storage Tutorial](https://developer.mozilla.org/docs/Web/API/Window/localStorage)
- [JSON Parsing and Handling](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/JSON)
Have fun building something useful and creative! 🚀

@ -1,30 +1,48 @@
# Browser Extension Project Part 3: Learn about Background Tasks and Performance
Ever wonder what makes some browser extensions feel snappy and responsive while others seem sluggish? The secret lies in what's happening behind the scenes. While users click around your extension's interface, there's a whole world of background processes quietly managing data fetching, icon updates, and system resources.
This is our final lesson in the browser extension series, and we're going to make your carbon footprint tracker work smoothly. You'll add dynamic icon updates and learn how to spot performance issues before they become problems. It's like tuning a race car - small optimizations can make a huge difference in how everything runs.
By the time we're done, you'll have a polished extension and understand the performance principles that separate good web apps from great ones. Let's dive into the world of browser optimization.
## Pre-Lecture Quiz
[Pre-lecture quiz](https://ff-quizzes.netlify.app/web/quiz/27)
### Introduction
In the last two lessons of this module, you learned how to build a form and display area for data fetched from an API. It's a very standard way of creating a web presence on the web. You even learned how to handle fetching data asynchronously. Your browser extension is very nearly complete.
In our previous lessons, you built a form, connected it to an API, and tackled asynchronous data fetching. Your extension is taking shape nicely.
It remains to manage some background tasks, including refreshing the color of the extension's icon, so this is a great time to talk about how the browser manages this kind of task. Let's think about these browser tasks in the context of the performance of your web assets as you build them.
Now we need to add the finishing touches - like making that extension icon change colors based on the carbon data. This reminds me of how NASA had to optimize every system on the Apollo spacecraft. They couldn't afford any wasted cycles or memory because lives depended on performance. While our browser extension isn't quite that critical, the same principles apply - efficient code creates better user experiences.
## Web Performance Basics
When your code runs efficiently, people can actually *feel* the difference. You know that moment when a page loads instantly or an animation flows smoothly? That's good performance at work.
Performance isn't just about speed - it's about making web experiences that feel natural instead of clunky and frustrating. Back in the early days of computing, Grace Hopper famously kept a nanosecond (a piece of wire about a foot long) on her desk to show how far light travels in one billionth of a second. It was her way of explaining why every microsecond matters in computing. Let's explore the detective tools that help you figure out what's slowing things down.
> "Website performance is about two things: how fast the page loads, and how fast the code on it runs." -- [Zack Grossbart](https://www.smashingmagazine.com/2012/06/javascript-profiling-chrome-developer-tools/)
The topic of how to make your websites blazingly fast on all kinds of devices, for all kinds of users, in all kinds of situations, is unsurprisingly vast. Here are some points to keep in mind as you build either a standard web project or a browser extension.
The first thing you need to do to ensure that your site is running efficiently is to gather data about its performance. The first place to do this is in the developer tools of your web browser. In Edge, you can select the "Settings and more" button (the three dots icon on the top right of the browser), then navigate to More Tools > Developer Tools and open the Performance tab. You can also use the keyboard shortcuts `Ctrl` + `Shift` + `I` on Windows or `Option` + `Command` + `I` on Mac to open developer tools.
The first step in optimizing your site is understanding what's actually happening under the hood. Fortunately, your browser comes with powerful detective tools built right in.
To open Developer Tools in Edge, click those three dots in the top right corner, then go to More Tools > Developer Tools. Or use the keyboard shortcut: `Ctrl` + `Shift` + `I` on Windows or `Option` + `Command` + `I` on Mac. Once you're there, click on the Performance tab - this is where you'll do your investigation.
The Performance tab contains a Profiling tool. Open a website (try, for example, [https://www.microsoft.com](https://www.microsoft.com/?WT.mc_id=academic-77807-sagibbon)) and click the 'Record' button, then refresh the site. Stop the recording at any time, and you will be able to see the routines that are generated to 'script', 'render', and 'paint' the site:
**Here's your performance detective toolkit:**
- **Open** Developer Tools (you'll use these constantly as a developer!)
- **Head** to the Performance tab - think of it as your web app's fitness tracker
- **Hit** that Record button and watch your page in action
- **Study** the results to spot what's slowing things down
Let's try this out. Open a website (Microsoft.com works well for this) and click that 'Record' button. Now refresh the page and watch the profiler capture everything that happens. When you stop recording, you'll see a detailed breakdown of how the browser 'scripts', 'renders', and 'paints' the site. It reminds me of how mission control monitors every system during a rocket launch - you get real-time data on exactly what's happening and when.
![Edge profiler](./images/profiler.png)
✅ Visit the [Microsoft Documentation](https://docs.microsoft.com/microsoft-edge/devtools-guide/performance/?WT.mc_id=academic-77807-sagibbon) on the Performance panel in Edge
The [Microsoft Documentation](https://docs.microsoft.com/microsoft-edge/devtools-guide/performance/?WT.mc_id=academic-77807-sagibbon) has tons more details if you want to dive deeper
> Tip: to get an accurate reading of your website's startup time, clear your browser's cache
> Pro tip: Clear your browser cache before testing to see how your site performs for first-time visitors - it's usually quite different from repeat visits!
Select elements of the profile timeline to zoom in on events that happen while your page loads.
@ -38,106 +56,179 @@ Check the Event Log pane to see if any event took longer than 15 ms:
✅ Get to know your profiler! Open the developer tools on this site and see if there are any bottlenecks. What's the slowest-loading asset? The fastest?
## Profiling checks
## What to Look For When Profiling
Running the profiler is just the beginning - the real skill is knowing what those colorful charts are actually telling you. Don't worry, you'll get the hang of reading them. Experienced developers have learned to spot the warning signs before they become full-blown problems.
In general, there are some "problem areas" that every web developer should watch for when building a site to avoid nasty surprises when it's time to deploy to production.
Let's talk about the usual suspects - the performance troublemakers that tend to sneak into web projects. Like how Marie Curie had to carefully monitor radiation levels in her lab, we need to watch for certain patterns that indicate trouble brewing. Catching these early will save you (and your users) a lot of frustration.
**Asset sizes**: The web has gotten 'heavier', and thus slower, over the past few years. Some of this weight has to do with the use of images.
**Asset sizes**: Websites have been getting "heavier" over the years, and a lot of that extra weight comes from images. It's like we've been stuffing more and more into our digital suitcases.
Look through the [Internet Archive](https://httparchive.org/reports/page-weight) for a historical view of page weight and more.
Check out the [Internet Archive](https://httparchive.org/reports/page-weight) to see how page sizes have grown over time - it's quite revealing.
A good practice is to ensure that your images are optimized and delivered at the right size and resolution for your users.
**Here's how to keep your assets optimized:**
- **Compress** those images! Modern formats like WebP can cut file sizes dramatically
- **Serve** the right image size for each device - no need to send huge desktop images to phones
- **Minify** your CSS and JavaScript - every byte counts
- **Use** lazy loading so images only download when users actually scroll to them
**DOM traversals**: The browser has to build its Document Object Model based on the code you write, so it's in the interest of good page performance to keep your tags minimal, only using and styling what the page needs. To this point, excess CSS associated with a page could be optimized; styles that need to be used only on one page don't need to be included in the main style sheet, for example.
**Key strategies for DOM optimization:**
- **Minimizes** the number of HTML elements and nesting levels
- **Removes** unused CSS rules and consolidates stylesheets efficiently
- **Organizes** CSS to load only what's needed for each page
- **Structures** HTML semantically for better browser parsing
**JavaScript**: Every JavaScript developer should watch for 'render-blocking' scripts that must be loaded before the rest of the DOM can be traversed and painted to the browser. Consider using `defer` with your inline scripts (as is done in the Terrarium module).
**Modern JavaScript optimization techniques:**
- **Uses** the `defer` attribute to load scripts after DOM parsing
- **Implements** code splitting to load only necessary JavaScript
- **Applies** lazy loading for non-critical functionality
- **Minimizes** the use of heavy libraries and frameworks when possible
✅ Try some sites on a [Site Speed Test website](https://www.webpagetest.org/) to learn more about the common checks that are done to determine site performance.
Now that you have an idea of how the browser renders the assets you send to it, let's look at the last few things you need to do to complete your extension:
### Create a function to calculate color
Working in `/src/index.js`, add a function called `calculateColor()` after the series of `const` variables you set to gain access to the DOM:
Now we'll create a function that turns numerical data into meaningful colors. Think of it like a traffic light system - green for clean energy, red for high carbon intensity.
This function will take the CO2 data from our API and determine what color best represents the environmental impact. It's similar to how scientists use color-coding in heat maps to visualize complex data patterns - from ocean temperatures to star formation. Let's add this to `/src/index.js`, right after those `const` variables we set up earlier:
```JavaScript
```javascript
function calculateColor(value) {
let co2Scale = [0, 150, 600, 750, 800];
let colors = ['#2AA364', '#F5EB4D', '#9E4229', '#381D02', '#381D02'];
// Define CO2 intensity scale (grams per kWh)
const co2Scale = [0, 150, 600, 750, 800];
// Corresponding colors from green (clean) to dark brown (high carbon)
const colors = ['#2AA364', '#F5EB4D', '#9E4229', '#381D02', '#381D02'];
let closestNum = co2Scale.sort((a, b) => {
// Find the closest scale value to our input
const closestNum = co2Scale.sort((a, b) => {
return Math.abs(a - value) - Math.abs(b - value);
})[0];
console.log(value + ' is closest to ' + closestNum);
let num = (element) => element > closestNum;
let scaleIndex = co2Scale.findIndex(num);
let closestColor = colors[scaleIndex];
console.log(`${value} is closest to ${closestNum}`);
// Find the index for color mapping
const num = (element) => element > closestNum;
const scaleIndex = co2Scale.findIndex(num);
const closestColor = colors[scaleIndex];
console.log(scaleIndex, closestColor);
// Send color update message to background script
chrome.runtime.sendMessage({ action: 'updateIcon', value: { color: closestColor } });
}
```
What's going on here? You pass in a value (the carbon intensity) from the API call you completed in the last lesson, and then you calculate how close its value is to the index presented in colors array. Then you send that closest color value over to the chrome runtime.
**Let's break down this clever little function:**
- **Sets up** two arrays - one for CO2 levels, another for colors (green = clean, brown = dirty!)
- **Finds** the closest match to our actual CO2 value using some neat array sorting
- **Grabs** the matching color using the findIndex() method
- **Sends** a message to Chrome's background script with our chosen color
- **Uses** template literals (those backticks) for cleaner string formatting
- **Keeps** everything organized with const declarations
The chrome.runtime has [an API](https://developer.chrome.com/extensions/runtime) that handles all kinds of background tasks, and your extension is leveraging that:
The `chrome.runtime` [API](https://developer.chrome.com/extensions/runtime) is like the nervous system of your extension - it handles all the behind-the-scenes communication and tasks:
> "Use the chrome.runtime API to retrieve the background page, return details about the manifest, and listen for and respond to events in the app or extension lifecycle. You can also use this API to convert the relative path of URLs to fully-qualified URLs."
**Why the Chrome Runtime API is so handy:**
- **Lets** different parts of your extension talk to each other
- **Handles** background work without freezing the user interface
- **Manages** your extension's lifecycle events
- **Makes** message passing between scripts super easy
✅ If you're developing this browser extension for Edge, it might surprise you that you're using a chrome API. The newer Edge browser versions run on the Chromium browser engine, so you can leverage these tools.
> Note, if you want to profile a browser extension, launch the dev tools from within the extension itself, as it is its own separate browser instance.
> **Pro Tip**: If you want to profile a browser extension, launch the dev tools from within the extension itself, as it is its own separate browser instance. This gives you access to extension-specific performance metrics.
### Set a default icon color
Now, in the `init()` function, set the icon to be generic green to start by again calling chrome's `updateIcon` action:
Before we start fetching real data, let's give our extension a starting point. Nobody likes staring at a blank or broken-looking icon. We'll start with a green color so users know the extension is working from the moment they install it.
In your `init()` function, let's set up that default green icon:
```JavaScript
```javascript
chrome.runtime.sendMessage({
action: 'updateIcon',
value: {
color: 'green',
},
value: {
color: 'green',
},
});
```
**What this initialization accomplishes:**
- **Sets** a neutral green color as the default state
- **Provides** immediate visual feedback when the extension loads
- **Establishes** the communication pattern with the background script
- **Ensures** users see a functional extension before data loads
### Call the function, execute the call
Next, call that function you just created by adding it to the promise returned by the C02Signal API:
Now let's connect everything together so that when fresh CO2 data comes in, your icon automatically updates with the right color. It's like connecting the final circuit in an electronic device - suddenly all the individual components work as one system.
Add this line right after you get the CO2 data from the API:
```JavaScript
//let CO2...
```javascript
// After retrieving CO2 data from the API
// let CO2 = data.data[0].intensity.actual;
calculateColor(CO2);
```
**This integration accomplishes:**
- **Connects** the API data flow with the visual indicator system
- **Triggers** icon updates automatically when new data arrives
- **Ensures** real-time visual feedback based on current carbon intensity
- **Maintains** the separation of concerns between data fetching and display logic
And finally, in `/dist/background.js`, add the listener for these background action calls:
```JavaScript
```javascript
// Listen for messages from the content script
chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
if (msg.action === 'updateIcon') {
chrome.action.setIcon({ imageData: drawIcon(msg.value) });
}
});
//borrowed from energy lollipop extension, nice feature!
// Draw dynamic icon using Canvas API
// Borrowed from energy lollipop extension - nice feature!
function drawIcon(value) {
let canvas = new OffscreenCanvas(200, 200);
let context = canvas.getContext('2d');
// Create an offscreen canvas for better performance
const canvas = new OffscreenCanvas(200, 200);
const context = canvas.getContext('2d');
// Draw a colored circle representing carbon intensity
context.beginPath();
context.fillStyle = value.color;
context.arc(100, 100, 50, 0, 2 * Math.PI);
context.fill();
// Return the image data for the browser icon
return context.getImageData(50, 50, 100, 100);
}
```
In this code, you are adding a listener for any messages coming to the backend task manager. If it's called 'updateIcon', then the next code is run to draw an icon of the proper color using the Canvas API.
**Here's what this background script does:**
- **Listens** for messages from your main script (like a receptionist taking calls)
- **Processes** those 'updateIcon' requests to change your toolbar icon
- **Creates** new icons on the fly using the Canvas API
- **Draws** a simple colored circle that shows the current carbon intensity
- **Updates** your browser toolbar with the fresh icon
- **Uses** OffscreenCanvas for smooth performance (no UI blocking)
✅ You'll learn more about the Canvas API in the [Space Game lessons](../../6-space-game/2-drawing-to-canvas/README.md).
Now, rebuild your extension (`npm run build`), refresh and launch your extension, and watch the color change. Is it a good time to run an errand or wash the dishes? Now you know!
**Time to test your extension:**
- **Build** everything with `npm run build`
- **Reload** your extension in the browser (don't forget this step)
- **Open** your extension and watch that icon change colors
- **Check** how it responds to real carbon data from around the world
Congratulations, you've built a useful browser extension and learned more about how the browser works and how to profile its performance.
Now you'll know at a glance whether it's a good time for that load of laundry or if you should wait for cleaner energy. You've just built something genuinely useful and learned about browser performance along the way.
## GitHub Copilot Agent Challenge 🚀
@ -147,13 +238,17 @@ Use the Agent mode to complete the following challenge:
**Prompt:** Create a performance monitoring system for the browser extension that measures and logs the time it takes to fetch CO2 data from the API, calculate colors, and update the icon. Add a function called `performanceTracker` that uses the Performance API to measure these operations and displays the results in the browser console with timestamps and duration metrics.
---
Learn more about [agent mode](https://code.visualstudio.com/blogs/2025/02/24/introducing-copilot-agent-mode) here.
## 🚀 Challenge
Investigate some open source websites that have been around a long time ago, and, based on their GitHub history, see if you can determine how they were optimized over the years for performance, if at all. What is the most common pain point?
Here's an interesting detective mission: pick a few open source websites that have been around for years (think Wikipedia, GitHub, or Stack Overflow) and dig into their commit history. Can you spot where they made performance improvements? What problems kept cropping up?
**Your investigation approach:**
- **Search** commit messages for words like "optimize," "performance," or "faster"
- **Look** for patterns - do they keep fixing the same types of issues?
- **Identify** the common culprits that slow down websites
- **Share** what you discover - other developers learn from real-world examples
## Post-Lecture Quiz

@ -1,9 +1,93 @@
# Analyze a site for performance
Provide a detailed report of a web site, showing areas where performance is problematic. Analyze why the site is slow and what you could do to speed it up. Don't rely only on the browser tools, but do some research on other tools that can help your report.
## Assignment Overview
Performance analysis is a critical skill for modern web developers. In this assignment, you'll conduct a comprehensive performance audit of a real website, using both browser-based tools and third-party services to identify bottlenecks and propose optimization strategies.
Your task is to provide a detailed performance report that demonstrates your understanding of web performance principles and your ability to use professional analysis tools effectively.
## Assignment Instructions
**Choose a website** for analysis - select one of the following options:
- A popular website you use frequently (news site, social media, e-commerce)
- An open-source project website (GitHub pages, documentation sites)
- A local business website or portfolio site
- Your own project or previous coursework
**Conduct multi-tool analysis** using at least three different approaches:
- **Browser DevTools** - Use Chrome/Edge Performance tab for detailed profiling
- **Online auditing tools** - Try Lighthouse, GTmetrix, or WebPageTest
- **Network analysis** - Examine resource loading, file sizes, and request patterns
**Document your findings** in a comprehensive report that includes:
### Performance Metrics Analysis
- **Load time measurements** from multiple tools and perspectives
- **Core Web Vitals** scores (LCP, FID, CLS) and their implications
- **Resource breakdown** showing which assets contribute most to load time
- **Network waterfall analysis** identifying blocking resources
### Problem Identification
- **Specific performance bottlenecks** with supporting data
- **Root cause analysis** explaining why each issue occurs
- **User impact assessment** describing how problems affect real users
- **Priority ranking** of issues based on severity and fixing difficulty
### Optimization Recommendations
- **Specific, actionable improvements** with expected impact
- **Implementation strategies** for each recommended change
- **Modern best practices** that could be applied (lazy loading, compression, etc.)
- **Tools and techniques** for ongoing performance monitoring
## Research Requirements
**Don't rely only on browser tools** - expand your analysis using:
**Third-party auditing services:**
- [Google Lighthouse](https://developers.google.com/web/tools/lighthouse) - Comprehensive audits
- [GTmetrix](https://gtmetrix.com/) - Performance and optimization insights
- [WebPageTest](https://www.webpagetest.org/) - Real-world testing conditions
- [Pingdom](https://tools.pingdom.com/) - Global performance monitoring
**Specialized analysis tools:**
- [Bundle Analyzer](https://bundlephobia.com/) - JavaScript bundle size analysis
- [Image optimization tools](https://squoosh.app/) - Asset optimization opportunities
- [Security headers analysis](https://securityheaders.com/) - Security performance impact
## Deliverables Format
Create a professional report (2-3 pages) that includes:
1. **Executive Summary** - Key findings and recommendations overview
2. **Methodology** - Tools used and testing approach
3. **Current Performance Assessment** - Baseline metrics and measurements
4. **Issues Identified** - Detailed problem analysis with supporting data
5. **Recommendations** - Prioritized improvement strategies
6. **Implementation Roadmap** - Step-by-step optimization plan
**Include visual evidence:**
- Screenshots of performance tools and metrics
- Charts or graphs showing performance data
- Before/after comparisons where possible
- Network waterfall charts and resource breakdowns
## Rubric
| Criteria | Exemplary | Adequate | Needs Improvement |
| -------- | ---------------------------------------------------------------------------------------------------------- | --------------------------- | ----------------------------- |
| | A report is presented with details drawn not only from browser tools but from 3rd party tools if available | A basic report is presented | A minimal report is presented |
| Criteria | Exemplary (90-100%) | Adequate (70-89%) | Needs Improvement (50-69%) |
| -------- | ------------------- | ----------------- | -------------------------- |
| **Analysis Depth** | Comprehensive analysis using 4+ tools with detailed metrics, root cause analysis, and user impact assessment | Good analysis using 3 tools with clear metrics and basic problem identification | Basic analysis using 2 tools with limited depth and minimal problem identification |
| **Tool Diversity** | Uses browser tools + 3+ third-party services with comparative analysis and insights from each | Uses browser tools + 2 third-party services with some comparative analysis | Uses browser tools + 1 third-party service with limited comparison |
| **Problem Identification** | Identifies 5+ specific performance issues with detailed root cause analysis and quantified impact | Identifies 3-4 performance issues with good analysis and some impact measurement | Identifies 1-2 performance issues with basic analysis |
| **Recommendations** | Provides specific, actionable recommendations with implementation details, expected impact, and modern best practices | Provides good recommendations with some implementation guidance and expected outcomes | Provides basic recommendations with limited implementation details |
| **Professional Presentation** | Well-organized report with clear structure, visual evidence, executive summary, and professional formatting | Good organization with some visual evidence and clear structure | Basic organization with minimal visual evidence |
## Learning Outcomes
By completing this assignment, you will demonstrate your ability to:
- **Apply** professional performance analysis tools and methodologies
- **Identify** performance bottlenecks using data-driven analysis
- **Analyze** the relationship between code quality and user experience
- **Recommend** specific, actionable optimization strategies
- **Communicate** technical findings in a professional format
This assignment reinforces the performance concepts learned in the lesson while building practical skills you'll use throughout your web development career.

@ -1,41 +1,50 @@
# Build a Space Game Part 1: Introduction
![video](../images/pewpew.gif)
![Space game animation showing gameplay](../images/pewpew.gif)
Just like NASA's mission control coordinates multiple systems during a space launch, we're going to build a space game that demonstrates how different parts of a program can work together seamlessly. While creating something you can actually play, you'll learn essential programming concepts that apply to any software project.
We'll explore two fundamental approaches to organizing code: inheritance and composition. These aren't just academic concepts they're the same patterns that power everything from video games to banking systems. We'll also implement a communication system called pub/sub that works like the communication networks used in spacecraft, allowing different components to share information without creating dependencies.
By the end of this series, you'll understand how to build applications that can scale and evolve whether you're developing games, web applications, or any other software system.
## Pre-Lecture Quiz
[Pre-lecture quiz](https://ff-quizzes.netlify.app/web/quiz/29)
### Inheritance and Composition in game development
## Inheritance and Composition in Game Development
In earlier lessons, there was not much need to worry about the design architecture of the apps you built, as the projects were very small in scope. However, when your applications grow in size and scope, architectural decisions become a larger concern. There are two major approaches to creating larger applications in JavaScript: *composition* or *inheritance*. There are pros and cons to both but let's explain them from within the context of a game.
As projects grow in complexity, code organization becomes critical. What begins as a simple script can become difficult to maintain without proper structure much like how the Apollo missions required careful coordination between thousands of components.
We'll explore two fundamental approaches for organizing code: inheritance and composition. Each has distinct advantages, and understanding both helps you choose the right approach for different situations. We'll demonstrate these concepts through our space game, where heroes, enemies, power-ups, and other objects must interact efficiently.
✅ One of the most famous programming books ever written has to do with [design patterns](https://en.wikipedia.org/wiki/Design_Patterns).
In a game you have `game objects` which are objects that exist on a screen. This means they have a location on a cartesian coordinate system, characterized by having an `x` and `y` coordinate. As you develop a game you will notice that all your game objects have a standard property, common for every game you create, namely elements that are:
In any game, you have `game objects` the interactive elements that populate your game world. Heroes, enemies, power-ups, and visual effects are all game objects. Each exists at specific screen coordinates using `x` and `y` values, similar to plotting points on a coordinate plane.
Despite their visual differences, these objects often share fundamental behaviors:
- **location-based** Most, if not all, game elements are location based. This means that they have a location, an `x` and `y`.
- **movable** These are objects that can move to a new location. This is typically a hero, a monster or an NPC (a non player character), but not for example, a static object like a tree.
- **self-destructing** These objects only exist for a set period of time before they set themselves up for deletion. Usually this is represented by a `dead` or `destroyed` boolean that signals to the game engine that this object should no longer be rendered.
- **cool-down** 'Cool-down' is a typical property among short-lived objects. A typical example is a piece of text or graphical effect like an explosion that should only be seen for a few milliseconds.
- **They exist somewhere** Every object has x and y coordinates so the game knows where to draw it
- **Many can move around** Heroes run, enemies chase, bullets fly across the screen
- **They have a lifespan** Some stick around forever, others (like explosions) appear briefly and vanish
- **They react to stuff** When things collide, power-ups get collected, health bars update
✅ Think about a game like Pac-Man. Can you identify the four object types listed above in this game?
### Expressing behavior
### Expressing Behavior Through Code
All we described above are behavior that game objects can have. So how do we encode those? We can express this behavior as methods associated to either classes or objects.
Now that you understand the common behaviors game objects share, let's explore how to implement these behaviors in JavaScript. You can express object behavior through methods attached to either classes or individual objects, and there are several approaches to choose from.
**Classes**
**The Class-Based Approach**
The idea is to use `classes` in conjunction with `inheritance` to accomplish adding a certain behavior to a class.
Classes and inheritance provide a structured approach to organizing game objects. Like the taxonomic classification system developed by Carl Linnaeus, you start with a base class containing common properties, then create specialized classes that inherit these fundamentals while adding specific capabilities.
✅ Inheritance is an important concept to understand. Learn more on [MDN's article about inheritance](https://developer.mozilla.org/docs/Web/JavaScript/Inheritance_and_the_prototype_chain).
Expressed via code, a game object can typically look like this:
Here's how you can implement game objects using classes and inheritance:
```javascript
//set up the class GameObject
// Step 1: Create the base GameObject class
class GameObject {
constructor(x, y, type) {
this.x = x;
@ -43,167 +52,279 @@ class GameObject {
this.type = type;
}
}
```
**Let's break this down step by step:**
- We're creating a basic template that every game object can use
- The constructor saves where the object is (`x`, `y`) and what kind of thing it is
- This becomes the foundation that all your game objects will build on
//this class will extend the GameObject's inherent class properties
```javascript
// Step 2: Add movement capability through inheritance
class Movable extends GameObject {
constructor(x,y, type) {
super(x,y, type)
constructor(x, y, type) {
super(x, y, type); // Call parent constructor
}
//this movable object can be moved on the screen
// Add the ability to move to a new position
moveTo(x, y) {
this.x = x;
this.y = y;
}
}
```
**In the above, we've:**
- **Extended** the GameObject class to add movement functionality
- **Called** the parent constructor using `super()` to initialize inherited properties
- **Added** a `moveTo()` method that updates the object's position
//this is a specific class that extends the Movable class, so it can take advantage of all the properties that it inherits
```javascript
// Step 3: Create specific game object types
class Hero extends Movable {
constructor(x,y) {
super(x,y, 'Hero')
constructor(x, y) {
super(x, y, 'Hero'); // Set type automatically
}
}
//this class, on the other hand, only inherits the GameObject properties
class Tree extends GameObject {
constructor(x,y) {
super(x,y, 'Tree')
constructor(x, y) {
super(x, y, 'Tree'); // Trees don't need movement
}
}
//a hero can move...
const hero = new Hero();
hero.moveTo(5,5);
// Step 4: Use your game objects
const hero = new Hero(0, 0);
hero.moveTo(5, 5); // Hero can move!
//but a tree cannot
const tree = new Tree();
const tree = new Tree(10, 15);
// tree.moveTo() would cause an error - trees can't move
```
**Understanding these concepts:**
- **Creates** specialized object types that inherit appropriate behaviors
- **Demonstrates** how inheritance allows selective feature inclusion
- **Shows** that heroes can move while trees remain stationary
- **Illustrates** how the class hierarchy prevents inappropriate actions
✅ Take a few minutes to re-envision a Pac-Man hero (Inky, Pinky or Blinky, for example) and how it would be written in JavaScript.
**Composition**
**The Composition Approach**
A different way of handling object inheritance is by using *Composition*. Then, objects express their behavior like this:
Composition follows a modular design philosophy, similar to how engineers design spacecraft with interchangeable components. Instead of inheriting from a parent class, you combine specific behaviors to create objects with exactly the functionality they need. This approach offers flexibility without rigid hierarchical constraints.
```javascript
//create a constant gameObject
// Step 1: Create base behavior objects
const gameObject = {
x: 0,
y: 0,
type: ''
};
//...and a constant movable
const movable = {
moveTo(x, y) {
this.x = x;
this.y = y;
}
}
//then the constant movableObject is composed of the gameObject and movable constants
const movableObject = {...gameObject, ...movable};
};
```
**Here's what this code does:**
- **Defines** a base `gameObject` with position and type properties
- **Creates** a separate `movable` behavior object with movement functionality
- **Separates** concerns by keeping position data and movement logic independent
//then create a function to create a new Hero who inherits the movableObject properties
```javascript
// Step 2: Compose objects by combining behaviors
const movableObject = { ...gameObject, ...movable };
// Step 3: Create factory functions for different object types
function createHero(x, y) {
return {
...movableObject,
x,
y,
type: 'Hero'
}
};
}
//...and a static object that inherits only the gameObject properties
function createStatic(x, y, type) {
return {
...gameObject
...gameObject,
x,
y,
type
}
};
}
//create the hero and move it
const hero = createHero(10,10);
hero.moveTo(5,5);
//and create a static tree which only stands around
const tree = createStatic(0,0, 'Tree');
```
**Which pattern should I use?**
**In the above, we've:**
- **Combined** base object properties with movement behavior using spread syntax
- **Created** factory functions that return customized objects
- **Enabled** flexible object creation without rigid class hierarchies
- **Allowed** objects to have exactly the behaviors they need
```javascript
// Step 4: Create and use your composed objects
const hero = createHero(10, 10);
hero.moveTo(5, 5); // Works perfectly!
const tree = createStatic(0, 0, 'Tree');
// tree.moveTo() is undefined - no movement behavior was composed
```
**Key points to remember:**
- **Composes** objects by mixing behaviors rather than inheriting them
- **Provides** more flexibility than rigid inheritance hierarchies
- **Allows** objects to have exactly the features they need
- **Uses** modern JavaScript spread syntax for clean object combination
```
**Which Pattern Should You Choose?**
It's up to you which pattern you choose. JavaScript supports both these paradigms.
> 💡 **Pro Tip**: Both patterns have their place in modern JavaScript development. Classes work well for clearly defined hierarchies, while composition shines when you need maximum flexibility.
>
**Here's when to use each approach:**
- **Choose** inheritance when you have clear "is-a" relationships (a Hero *is-a* Movable object)
- **Select** composition when you need "has-a" relationships (a Hero *has* movement abilities)
- **Consider** your team's preferences and project requirements
- **Remember** that you can mix both approaches in the same application
--
## Communication Patterns: The Pub/Sub System
Another pattern common in game development addresses the problem of handling the game's user experience and performance.
As applications grow complex, managing communication between components becomes challenging. The publish-subscribe pattern (pub/sub) solves this problem using principles similar to radio broadcasting one transmitter can reach multiple receivers without knowing who's listening.
## Pub/sub pattern
Consider what happens when a hero takes damage: the health bar updates, sound effects play, visual feedback appears. Rather than coupling the hero object directly to these systems, pub/sub allows the hero to broadcast a "damage taken" message. Any system that needs to respond can subscribe to this message type and react accordingly.
✅ Pub/Sub stands for 'publish-subscribe'
**Pub/Sub** stands for 'publish-subscribe'
This pattern addresses the idea that the disparate parts of your application shouldn't know about one another. Why is that? It makes it a lot easier to see what's going on in general if various parts are separated. It also makes it easier to suddenly change behavior if you need to. How do we accomplish this? We do this by establishing some concepts:
### Understanding the Pub/Sub Architecture
- **message**: A message is usually a text string accompanied by an optional payload (a piece of data that clarifies what the message is about). A typical message in a game can be `KEY_PRESSED_ENTER`.
- **publisher**: This element *publishes* a message and sends it out to all subscribers.
- **subscriber**: This element *listens* to specific messages and carries out some task as the result of receiving this message, such as firing a laser.
The pub/sub pattern keeps different parts of your application loosely coupled, meaning they can work together without being directly dependent on each other. This separation makes your code more maintainable, testable, and flexible to changes.
The implementation is quite small in size but it's a very powerful pattern. Here's how it can be implemented:
**The key players in pub/sub:**
- **Messages** Simple text labels like `'PLAYER_SCORED'` that describe what happened (plus any extra info)
- **Publishers** The objects that shout out "Something happened!" to anyone who's listening
- **Subscribers** The objects that say "I care about that event" and react when it happens
- **Event System** The middleman that makes sure messages get to the right listeners
### Building an Event System
Let's create a simple but powerful event system that demonstrates these concepts:
```javascript
//set up an EventEmitter class that contains listeners
// Step 1: Create the EventEmitter class
class EventEmitter {
constructor() {
this.listeners = {};
this.listeners = {}; // Store all event listeners
}
//when a message is received, let the listener to handle its payload
// Register a listener for a specific message type
on(message, listener) {
if (!this.listeners[message]) {
this.listeners[message] = [];
}
this.listeners[message].push(listener);
}
//when a message is sent, send it to a listener with some payload
// Send a message to all registered listeners
emit(message, payload = null) {
if (this.listeners[message]) {
this.listeners[message].forEach(l => l(message, payload))
this.listeners[message].forEach(listener => {
listener(message, payload);
});
}
}
}
```
To use the above code we can create a very small implementation:
**Breaking down what happens here:**
- **Creates** a central event management system using a simple class
- **Stores** listeners in an object organized by message type
- **Registers** new listeners using the `on()` method
- **Broadcasts** messages to all interested listeners using `emit()`
- **Supports** optional data payloads for passing relevant information
### Putting It All Together: A Practical Example
Alright, let's see this in action! We'll build a simple movement system that shows how clean and flexible pub/sub can be:
```javascript
//set up a message structure
// Step 1: Define your message types
const Messages = {
HERO_MOVE_LEFT: 'HERO_MOVE_LEFT'
HERO_MOVE_LEFT: 'HERO_MOVE_LEFT',
HERO_MOVE_RIGHT: 'HERO_MOVE_RIGHT',
ENEMY_SPOTTED: 'ENEMY_SPOTTED'
};
//invoke the eventEmitter you set up above
// Step 2: Create your event system and game objects
const eventEmitter = new EventEmitter();
//set up a hero
const hero = createHero(0,0);
//let the eventEmitter know to watch for messages pertaining to the hero moving left, and act on it
const hero = createHero(0, 0);
```
**Here's what this code does:**
- **Defines** a constants object to prevent typos in message names
- **Creates** an event emitter instance to handle all communication
- **Initializes** a hero object at the starting position
```javascript
// Step 3: Set up event listeners (subscribers)
eventEmitter.on(Messages.HERO_MOVE_LEFT, () => {
hero.move(5,0);
hero.moveTo(hero.x - 5, hero.y);
console.log(`Hero moved to position: ${hero.x}, ${hero.y}`);
});
//set up the window to listen for the keyup event, specifically if the left arrow is hit, emit a message to move the hero left
window.addEventListener('keyup', (evt) => {
if (evt.key === 'ArrowLeft') {
eventEmitter.emit(Messages.HERO_MOVE_LEFT)
}
eventEmitter.on(Messages.HERO_MOVE_RIGHT, () => {
hero.moveTo(hero.x + 5, hero.y);
console.log(`Hero moved to position: ${hero.x}, ${hero.y}`);
});
```
Above we connect a keyboard event, `ArrowLeft` and send the `HERO_MOVE_LEFT` message. We listen to that message and move the `hero` as a result. The strength with this pattern is that the event listener and the hero don't know about each other. You can remap the `ArrowLeft` to the `A` key. Additionally it would be possible to do something completely different on `ArrowLeft` by making a few edits to the eventEmitter's `on` function:
**In the above, we've:**
- **Registered** event listeners that respond to movement messages
- **Updated** the hero's position based on the movement direction
- **Added** console logging to track the hero's position changes
- **Separated** the movement logic from the input handling
```javascript
eventEmitter.on(Messages.HERO_MOVE_LEFT, () => {
hero.move(5,0);
// Step 4: Connect keyboard input to events (publishers)
window.addEventListener('keydown', (event) => {
switch(event.key) {
case 'ArrowLeft':
eventEmitter.emit(Messages.HERO_MOVE_LEFT);
break;
case 'ArrowRight':
eventEmitter.emit(Messages.HERO_MOVE_RIGHT);
break;
}
});
```
As things gets more complicated when your game grows, this pattern stays the same in complexity and your code stays clean. It's really recommended to adopt this pattern.
**Understanding these concepts:**
- **Connects** keyboard input to game events without tight coupling
- **Enables** the input system to communicate with game objects indirectly
- **Allows** multiple systems to respond to the same keyboard events
- **Makes** it easy to change key bindings or add new input methods
> 💡 **Pro Tip**: The beauty of this pattern is flexibility! You can easily add sound effects, screen shake, or particle effects by simply adding more event listeners no need to modify the existing keyboard or movement code.
>
**Here's why you'll love this approach:**
- Adding new features becomes super easy just listen for the events you care about
- Multiple things can react to the same event without stepping on each other
- Testing gets way simpler because each piece works independently
- When something breaks, you know exactly where to look
### Why Pub/Sub Scales Effectively
The pub/sub pattern maintains simplicity as applications grow in complexity. Whether managing dozens of enemies, dynamic UI updates, or sound systems, the pattern handles increased scale without architectural changes. New features integrate into the existing event system without affecting established functionality.
> ⚠️ **Common Mistake**: Don't create too many specific message types early on. Start with broad categories and refine them as your game's needs become clearer.
>
**Best practices to follow:**
- **Groups** related messages into logical categories
- **Uses** descriptive names that clearly indicate what happened
- **Keeps** message payloads simple and focused
- **Documents** your message types for team collaboration
---
@ -215,9 +336,11 @@ Use the Agent mode to complete the following challenge:
**Prompt:** Create a JavaScript game system with the following requirements: 1) Create a base GameObject class with x, y coordinates and a type property. 2) Create a Hero class that extends GameObject and can move. 3) Create an Enemy class that extends GameObject and can chase the hero. 4) Implement an EventEmitter class for the pub/sub pattern. 5) Set up event listeners so when the hero moves, nearby enemies receive a 'HERO_MOVED' event and update their position to move toward the hero. Include console.log statements to show the communication between objects.
Learn more about [agent mode](https://code.visualstudio.com/blogs/2025/02/24/introducing-copilot-agent-mode) here.
## 🚀 Challenge
Think about how the pub-sub pattern can enhance a game. Which parts should emit events, and how should the game react to them? Now's your chance to get creative, thinking of a new game and how its parts might behave.
Consider how the pub-sub pattern can enhance game architecture. Identify which components should emit events and how the system should respond. Design a game concept and map out the communication patterns between its components.
## Post-Lecture Quiz

@ -1,11 +1,86 @@
# Mock up a game
# Mock up a Game: Apply Design Patterns
## Assignment Overview
Put your newfound knowledge of design patterns to work by creating a simple game prototype! This assignment will help you practice both architectural patterns (inheritance or composition) and the pub/sub communication system you learned about in the lesson.
## Instructions
Using the code samples in the lesson, write a representation of a game you enjoy. It will have to be a simple game, but the goal is to use either the class or the composition pattern and the pub/sub pattern to show how a game might launch. Get creative!
Create a simple game representation that demonstrates the design patterns from this lesson. Your game should be functional but doesn't need complex graphics \u2013 focus on the underlying architecture and communication patterns.
### Requirements
**Choose Your Architecture Pattern:**
- **Option A**: Use class-based inheritance (like the `GameObject``Movable``Hero` example)
- **Option B**: Use composition (like the factory function approach with mixed behaviors)
**Implement Communication:**
- **Include** an `EventEmitter` class for pub/sub messaging
- **Set up** at least 2-3 different message types (like `PLAYER_MOVE`, `ENEMY_SPAWN`, `SCORE_UPDATE`)
- **Connect** user input (keyboard/mouse) to game events through the event system
**Game Elements to Include:**
- At least one player-controlled character
- At least one other game object (enemy, collectible, or environmental element)
- Basic interaction between objects (collision, collection, or communication)
### Suggested Game Ideas
**Simple Games to Consider:**
- **Snake Game** \u2013 Snake segments follow the head, food spawns randomly
- **Pong Variation** \u2013 Paddle responds to input, ball bounces off walls
- **Collector Game** \u2013 Player moves around collecting items while avoiding obstacles
- **Tower Defense Basics** \u2013 Towers detect and shoot at moving enemies
### Code Structure Guidelines
```javascript
// Example starting structure
const Messages = {
// Define your game messages here
};
class EventEmitter {
// Your event system implementation
}
// Choose either class-based OR composition approach
// Class-based example:
class GameObject { /* base properties */ }
class Player extends GameObject { /* player-specific behavior */ }
// OR Composition example:
const gameObject = { /* base properties */ };
const movable = { /* movement behavior */ };
function createPlayer() { /* combine behaviors */ }
```
### Testing Your Implementation
**Verify your code works by:**
- **Testing** that objects move or change when events are triggered
- **Confirming** that multiple objects can respond to the same event
- **Checking** that you can add new behaviors without modifying existing code
- **Ensuring** keyboard/mouse input properly triggers game events
## Submission Guidelines
**Your submission should include:**
1. **JavaScript file(s)** with your game implementation
2. **HTML file** to run and test your game (can be simple)
3. **Comments** explaining which pattern you chose and why
4. **Brief documentation** of your message types and what they do
## Rubric
| Criteria | Exemplary | Adequate | Needs Improvement |
| -------- | ------------------------------------------------------- | ----------------------------------------------------- | --------------------------------------------------- |
| | Three elements are placed on the screen and manipulated | Two elements are placed on the screen and manipulated | One element is placed on the screen and manipulated |
| Criteria | Exemplary (3 points) | Adequate (2 points) | Needs Improvement (1 point) |
|----------|---------------------|---------------------|------------------------------|
| **Architecture Pattern** | Correctly implements either inheritance OR composition with clear class/object hierarchy | Uses chosen pattern with minor issues or inconsistencies | Attempts to use pattern but implementation has significant problems |
| **Pub/Sub Implementation** | EventEmitter works correctly with multiple message types and proper event flow | Basic pub/sub system works with some event handling | Event system present but doesn't work reliably |
| **Game Functionality** | Three or more interactive elements that communicate through events | Two interactive elements with basic event communication | One element responds to events or basic interaction |
| **Code Quality** | Clean, well-commented code with logical organization and modern JavaScript | Generally well-organized code with adequate comments | Code works but lacks organization or clear commenting |
**Bonus Points:**
- **Creative game mechanics** that showcase interesting uses of the patterns
- **Multiple input methods** (keyboard AND mouse events)
- **Scalable architecture** that would be easy to extend with new features

@ -1,12 +1,20 @@
# Build a Space Game Part 2: Draw Hero and Monsters to Canvas
The Canvas API is one of web development's most powerful features for creating dynamic, interactive graphics right in your browser. In this lesson, we'll transform that blank HTML `<canvas>` element into a game world filled with heroes and monsters. Think of the canvas as your digital art board where code becomes visual.
We're building on what you learned in the previous lesson, and now we'll dive into the visual aspects. You'll learn how to load and display game sprites, position elements precisely, and create the visual foundation for your space game. This bridges the gap between static web pages and dynamic, interactive experiences.
By the end of this lesson, you'll have a complete game scene with your hero ship positioned correctly and enemy formations ready for battle. You'll understand how modern games render graphics in browsers and gain skills to create your own interactive visual experiences. Let's explore canvas graphics and bring your space game to life!
## Pre-Lecture Quiz
[Pre-lecture quiz](https://ff-quizzes.netlify.app/web/quiz/31)
## The Canvas
The canvas is an HTML element that by default has no content; it's a blank slate. You need to add to it by drawing on it.
So what exactly is this `<canvas>` element? It's HTML5's solution for creating dynamic graphics and animations in web browsers. Unlike regular images or videos that are static, the canvas gives you pixel-level control over everything that appears on screen. This makes it perfect for games, data visualizations, and interactive art. Think of it as a programmable drawing surface where JavaScript becomes your paintbrush.
By default, a canvas element looks like a blank, transparent rectangle on your page. But that's where the potential lies! Its real power emerges when you use JavaScript to draw shapes, load images, create animations, and make things respond to user interactions. It's similar to how early computer graphics pioneers at Bell Labs in the 1960s had to program every pixel to create the first digital animations.
✅ Read [more about the Canvas API](https://developer.mozilla.org/docs/Web/API/Canvas_API) on MDN.
@ -16,42 +24,46 @@ Here's how it's typically declared, as part of the page's body:
<canvas id="myCanvas" width="200" height="100"></canvas>
```
Above we are setting the `id`, `width` and `height`.
**Here's what this code does:**
- **Sets** the `id` attribute so you can reference this specific canvas element in JavaScript
- **Defines** the `width` in pixels to control the canvas's horizontal size
- **Establishes** the `height` in pixels to determine the canvas's vertical dimensions
- `id`: set this so you can obtain a reference when you need to interact with it.
- `width`: this is the width of the element.
- `height`: this is the height of the element.
## Drawing Simple Geometry
## Drawing simple geometry
Now that you know what the canvas element is, let's explore actually drawing on it! The canvas uses a coordinate system that might feel familiar from math class, but there's one important twist specific to computer graphics.
The Canvas is using a cartesian coordinate system to draw things. Thus it uses an x-axis and y-axis to express where something is located. The location `0,0` is the top left position and the bottom right is what you said to be the WIDTH and HEIGHT of the canvas.
The canvas uses Cartesian coordinates with an x-axis (horizontal) and y-axis (vertical) to position everything you draw. But here's the key difference: unlike the coordinate system from math class, the origin point `(0,0)` starts at the top-left corner, with x-values increasing as you move right and y-values increasing as you move down. This approach dates back to early computer displays where electron beams scanned from top to bottom, making top-left the natural starting point.
![the canvas's grid](canvas_grid.png)
> Image from [MDN](https://developer.mozilla.org/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes)
To draw on the canvas element you will need to go through the following steps:
To draw on the canvas element, you'll follow the same three-step process that forms the foundation of all canvas graphics. Once you do this a few times, it becomes second nature:
1. **Get a reference** to the Canvas element.
1. **Get a reference** on the Context element that sits on the canvas element.
1. **Perform a drawing operation** using the context element.
1. **Get a reference** to your Canvas element from the DOM (just like any other HTML element)
2. **Get the 2D rendering context** this provides all the drawing methods
3. **Start drawing!** Use the context's built-in methods to create your graphics
Code for the above steps usually looks like so:
Here's how this looks in code:
```javascript
// draws a red rectangle
//1. get the canvas reference
canvas = document.getElementById("myCanvas");
// Step 1: Get the canvas element
const canvas = document.getElementById("myCanvas");
//2. set the context to 2D to draw basic shapes
ctx = canvas.getContext("2d");
// Step 2: Get the 2D rendering context
const ctx = canvas.getContext("2d");
//3. fill it with the color red
// Step 3: Set fill color and draw a rectangle
ctx.fillStyle = 'red';
//4. and draw a rectangle with these parameters, setting location and size
ctx.fillRect(0,0, 200, 200) // x,y,width, height
ctx.fillRect(0, 0, 200, 200); // x, y, width, height
```
**Let's break this down step by step:**
- We **grab** our canvas element using its ID and store it in a variable
- We **get** the 2D rendering context this is our toolkit full of drawing methods
- We **tell** the canvas we want to fill things with red using the `fillStyle` property
- We **draw** a rectangle starting at the top-left corner (0,0) that's 200 pixels wide and tall
✅ The Canvas API mostly focuses on 2D shapes, but you can also draw 3D elements to a web site; for this, you might use the [WebGL API](https://developer.mozilla.org/docs/Web/API/WebGL_API).
You can draw all sorts of things with the Canvas API like:
@ -62,62 +74,98 @@ You can draw all sorts of things with the Canvas API like:
✅ Try it! You know how to draw a rectangle, can you draw a circle to a page? Take a look at some interesting Canvas drawings on CodePen. Here's a [particularly impressive example](https://codepen.io/dissimulate/pen/KrAwx).
## Load and draw an image asset
## Load and Draw an Image Asset
Drawing basic shapes is useful for getting started, but most games need actual images! Sprites, backgrounds, and textures are what give games their visual appeal. Loading and displaying images on the canvas works differently than drawing geometric shapes, but it's straightforward once you understand the process.
You load an image asset by creating an `Image` object and set its `src` property. Then you listen to the `load` event to know when it's ready to be used. The code looks like this:
We need to create an `Image` object, load our image file (this happens asynchronously, meaning "in the background"), and then draw it to the canvas once it's ready. This approach ensures your images display properly without blocking your application while they load.
### Load asset
### Basic Image Loading
```javascript
const img = new Image();
img.src = 'path/to/my/image.png';
img.onload = () => {
// image loaded and ready to be used
}
// Image loaded and ready to be used
console.log('Image loaded successfully!');
};
```
### Load asset pattern
**Here's what's happening in this code:**
- We **create** a brand new Image object to hold our sprite or texture
- We **tell** it which image file to load by setting the source path
- We **listen** for the load event so we know exactly when the image is ready to use
### A Better Way to Load Images
It's recommended to wrap the above in a construct like so, so it's easier to use and you only try to manipulate it when it's fully loaded:
Here's a more robust way to handle image loading that professional developers commonly use. We'll wrap the image loading in a Promise-based function this approach, popularized when JavaScript Promises became standard in ES6, makes your code more organized and handles errors gracefully:
```javascript
function loadAsset(path) {
return new Promise((resolve) => {
return new Promise((resolve, reject) => {
const img = new Image();
img.src = path;
img.onload = () => {
// image loaded and ready to be used
resolve(img);
}
})
};
img.onerror = () => {
reject(new Error(`Failed to load image: ${path}`));
};
});
}
// use like so
async function run() {
const heroImg = await loadAsset('hero.png')
const monsterImg = await loadAsset('monster.png')
// Modern usage with async/await
async function initializeGame() {
try {
const heroImg = await loadAsset('hero.png');
const monsterImg = await loadAsset('monster.png');
// Images are now ready to use
} catch (error) {
console.error('Failed to load game assets:', error);
}
}
```
To draw game assets to a screen, your code would look like this:
**What we've done here:**
- **Wrapped** all that image loading logic in a Promise so we can handle it better
- **Added** error handling that actually tells us when something goes wrong
- **Used** modern async/await syntax because it's so much cleaner to read
- **Included** try/catch blocks to gracefully handle any loading hiccups
Once your images are loaded, drawing them to the canvas is actually pretty straightforward:
```javascript
async function run() {
const heroImg = await loadAsset('hero.png')
const monsterImg = await loadAsset('monster.png')
canvas = document.getElementById("myCanvas");
ctx = canvas.getContext("2d");
ctx.drawImage(heroImg, canvas.width/2,canvas.height/2);
ctx.drawImage(monsterImg, 0,0);
async function renderGameScreen() {
try {
// Load game assets
const heroImg = await loadAsset('hero.png');
const monsterImg = await loadAsset('monster.png');
// Get canvas and context
const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");
// Draw images to specific positions
ctx.drawImage(heroImg, canvas.width / 2, canvas.height / 2);
ctx.drawImage(monsterImg, 0, 0);
} catch (error) {
console.error('Failed to render game screen:', error);
}
}
```
## Now it's time to start building your game
**Let's walk through this step by step:**
- We **load** both our hero and monster images in the background using await
- We **grab** our canvas element and get that 2D rendering context we need
- We **position** the hero image right in the center using some quick coordinate math
- We **plop** the monster image at the top-left corner to start our enemy formation
- We **catch** any errors that might happen during loading or rendering
## Now It's Time to Start Building Your Game
### What to build
Now we'll put everything together to create the visual foundation of your space game. You have a solid understanding of canvas fundamentals and image loading techniques, so this hands-on section will guide you through building a complete game screen with properly positioned sprites.
### What to Build
You will build a web page with a Canvas element. It should render a black screen `1024*768`. We've provided you with two images:
@ -131,61 +179,88 @@ You will build a web page with a Canvas element. It should render a black screen
### Recommended steps to start development
Locate the files that have been created for you in the `your-work` sub folder. It should contain the following:
Locate the starter files that have been created for you in the `your-work` sub folder. Your project structure should contain:
```bash
-| assets
-| enemyShip.png
-| player.png
-| index.html
-| app.js
-| package.json
your-work/
├── assets/
│ ├── enemyShip.png
│ └── player.png
├── index.html
├── app.js
└── package.json
```
Open the copy of this folder in Visual Studio Code. You need to have a local development environment setup, preferably with Visual Studio Code with NPM and Node installed. If you don't have `npm` set up on your computer, [here's how to do that](https://www.npmjs.com/get-npm).
**Here's what you're working with:**
- **Game sprites** live in the `assets/` folder so everything stays organized
- **Your main HTML file** sets up the canvas element and gets everything ready
- **A JavaScript file** where you'll write all your game rendering magic
- **A package.json** that sets up a development server so you can test locally
Open this folder in Visual Studio Code to begin development. You'll need a local development environment with Visual Studio Code, NPM, and Node.js installed. If you don't have `npm` set up on your computer, [here's how to install it](https://www.npmjs.com/get-npm).
Start your project by navigating to the `your_work` folder:
Start your development server by navigating to the `your-work` folder:
```bash
cd your-work
npm start
```
The above will start a HTTP Server on address `http://localhost:5000`. Open up a browser and input that address. It's a blank page right now, but that will change
**This command does some pretty cool stuff:**
- **Starts up** a local server at `http://localhost:5000` so you can test your game
- **Serves** all your files properly so your browser can load them correctly
- **Watches** your files for changes so you can develop smoothly
- **Gives you** a professional development environment to test everything
> Note: to see changes on your screen, refresh your browser.
> 💡 **Note**: Your browser will show a blank page initially that's expected! As you add code, refresh your browser to see your changes. This iterative development approach is similar to how NASA built the Apollo guidance computer testing each component before integrating it into the larger system.
### Add code
Add the needed code to `your-work/app.js` to solve the below
Add the required code to `your-work/app.js` to complete the following tasks:
1. **Draw a canvas with black background**
> 💡 **Here's how**: Find the TODO in `/app.js` and add just two lines. Set `ctx.fillStyle` to black, then use `ctx.fillRect()` starting at (0,0) with your canvas dimensions. Easy!
2. **Load game textures**
> 💡 **Here's how**: Use `await loadAsset()` to load your player and enemy images. Store them in variables so you can use them later. Remember they won't show up until you actually draw them!
1. **Draw** a canvas with black background
> tip: add two lines under the appropriate TODO in `/app.js`, setting the `ctx` element to be black and the top/left coordinates to be at 0,0 and the height and width to equal that of the canvas.
2. **Load** textures
> tip: add the player and enemy images using `await loadTexture` and passing in the image path. You won't see them on the screen yet!
3. **Draw** hero in the center of the screen in the bottom half
> tip: use the `drawImage` API to draw heroImg to the screen, setting `canvas.width / 2 - 45` and `canvas.height - canvas.height / 4)`;
4. **Draw** 5*5 monsters
> tip: Now you can uncomment the code to draw enemies on the screen. Next, go to the `createEnemies` function and build it out.
3. **Draw hero ship in the center-bottom position**
> 💡 **Here's how**: Use `ctx.drawImage()` to position your hero. For the x-coordinate, try `canvas.width / 2 - 45` to center it, and for y-coordinate use `canvas.height - canvas.height / 4` to put it in the bottom area.
First, set up some constants:
4. **Draw a 5×5 formation of enemy ships**
> 💡 **Here's how**: Find the `createEnemies` function and set up a nested loop. You'll need to do some math for spacing and positioning, but don't worry I'll show you exactly how!
```javascript
const MONSTER_TOTAL = 5;
const MONSTER_WIDTH = MONSTER_TOTAL * 98;
const START_X = (canvas.width - MONSTER_WIDTH) / 2;
const STOP_X = START_X + MONSTER_WIDTH;
```
First, establish constants for proper enemy formation layout:
```javascript
const ENEMY_TOTAL = 5;
const ENEMY_SPACING = 98;
const FORMATION_WIDTH = ENEMY_TOTAL * ENEMY_SPACING;
const START_X = (canvas.width - FORMATION_WIDTH) / 2;
const STOP_X = START_X + FORMATION_WIDTH;
```
then, create a loop to draw the array of monsters onto the screen:
**Let's break down what these constants do:**
- We **set** 5 enemies per row and column (a nice 5×5 grid)
- We **define** how much space to put between enemies so they don't look cramped
- We **calculate** how wide our whole formation will be
- We **figure out** where to start and stop so the formation looks centered
```javascript
for (let x = START_X; x < STOP_X; x += 98) {
for (let y = 0; y < 50 * 5; y += 50) {
ctx.drawImage(enemyImg, x, y);
}
}
```
Then, create nested loops to draw the enemy formation:
```javascript
for (let x = START_X; x < STOP_X; x += ENEMY_SPACING) {
for (let y = 0; y < 50 * 5; y += 50) {
ctx.drawImage(enemyImg, x, y);
}
}
```
**Here's what this nested loop does:**
- The outer loop **moves** from left to right across our formation
- The inner loop **goes** from top to bottom to create neat rows
- We **draw** each enemy sprite at the exact x,y coordinates we calculated
- Everything stays **evenly spaced** so it looks professional and organized
## Result
@ -207,6 +282,8 @@ Use the Agent mode to complete the following challenge:
**Prompt:** Create a new file called `enhanced-canvas.html` with a canvas that displays animated stars in the background, a pulsing health bar for the hero ship, and enemy ships that slowly move downward. Include JavaScript code that draws twinkling stars using random positions and opacity, implements a health bar that changes color based on health level (green > yellow > red), and animates the enemy ships to move down the screen at different speeds.
Learn more about [agent mode](https://code.visualstudio.com/blogs/2025/02/24/introducing-copilot-agent-mode) here.
## 🚀 Challenge
You've learned about drawing with the 2D-focused Canvas API; take a look at the [WebGL API](https://developer.mozilla.org/docs/Web/API/WebGL_API), and try to draw a 3D object.

@ -1,11 +1,67 @@
# Play with the Canvas API
# Assignment: Explore the Canvas API
## Learning Objectives
By completing this assignment, you will demonstrate your understanding of Canvas API fundamentals and apply creative problem-solving to build visual elements using JavaScript and HTML5 canvas.
## Instructions
Pick one element of the Canvas API and create something interesting around it. Can you create a little galaxy of repeated stars? Can you create an interesting texture of colored lines? You can look at CodePen for inspiration (but don't copy)
Choose one aspect of the Canvas API that interests you and create an engaging visual project around it. This assignment encourages you to experiment with the drawing capabilities you've learned while building something uniquely yours.
### Project Ideas to Inspire You
**Geometric Patterns:**
- **Create** a galaxy of animated twinkling stars using random positioning
- **Design** an interesting texture using repeated geometric shapes
- **Build** a kaleidoscope effect with rotating, colorful patterns
**Interactive Elements:**
- **Develop** a drawing tool that responds to mouse movements
- **Implement** shapes that change color when clicked
- **Design** a simple animation loop with moving elements
**Game-Related Graphics:**
- **Craft** a scrolling background for a space game
- **Build** particle effects like explosions or magic spells
- **Create** animated sprites with multiple frames
### Development Guidelines
**Research and Inspiration:**
- **Browse** CodePen for creative canvas examples (for inspiration, not copying)
- **Study** the [Canvas API documentation](https://developer.mozilla.org/docs/Web/API/Canvas_API) for additional methods
- **Experiment** with different drawing functions, colors, and animations
**Technical Requirements:**
- **Use** proper canvas setup with `getContext('2d')`
- **Include** meaningful comments explaining your approach
- **Test** your code thoroughly to ensure it runs without errors
- **Apply** modern JavaScript syntax (const/let, arrow functions)
**Creative Expression:**
- **Focus** on one Canvas API feature but explore it deeply
- **Add** your own creative twist to make the project personal
- **Consider** how your creation could be part of a larger application
### Submission Guidelines
Submit your completed project as a single HTML file with embedded CSS and JavaScript, or as separate files in a folder. Include a brief comment explaining your creative choices and the Canvas API features you explored.
## Rubric
| Criteria | Exemplary | Adequate | Needs Improvement |
| -------- | --------------------------------------------------------- | ----------------------------------- | --------------------- |
| | Code is submitted showing an interesting texture or shape | Code is submitted, but does not run | Code is not submitted |
| Criteria | Exemplary | Adequate | Needs Improvement |
|----------|-----------|----------|-------------------|
| **Technical Implementation** | Canvas API used creatively with multiple features, code runs flawlessly, modern JavaScript syntax applied | Canvas API used correctly, code runs with minor issues, basic implementation | Canvas API attempted but code has errors or doesn't execute |
| **Creativity and Design** | Highly original concept with polished visual appeal, demonstrates deep exploration of chosen Canvas feature | Good use of Canvas features with some creative elements, solid visual result | Basic implementation with minimal creativity or visual appeal |
| **Code Quality** | Well-organized, commented code following best practices, efficient algorithms | Clean code with some comments, follows basic coding standards | Code lacks organization, minimal comments, inefficient implementation |
## Reflection Questions
After completing your project, consider these questions:
1. **What Canvas API feature did you choose and why?**
2. **What challenges did you encounter while building your project?**
3. **How could you extend this project into a larger application or game?**
4. **What other Canvas API features would you like to explore next?**
> 💡 **Pro Tip**: Start simple and gradually add complexity. A well-executed simple project is better than an overly ambitious project that doesn't work properly!

@ -1,67 +1,97 @@
# Build a Space Game Part 3: Adding Motion
Think about your favorite games what makes them captivating isn't just pretty graphics, it's the way everything moves and responds to your actions. Right now, your space game is like a beautiful painting, but we're about to add movement that brings it to life.
When NASA's engineers programmed the guidance computer for the Apollo missions, they faced a similar challenge: how do you make a spacecraft respond to pilot input while automatically maintaining course corrections? The principles we'll learn today echo those same concepts managing player-controlled movement alongside automatic system behaviors.
In this lesson, you'll learn how to make spaceships glide across the screen, respond to player commands, and create smooth movement patterns. We'll break everything down into manageable concepts that build on each other naturally.
By the end, you'll have players flying their hero ship around the screen while enemy vessels patrol overhead. More importantly, you'll understand the core principles that power game movement systems.
## Pre-Lecture Quiz
[Pre-lecture quiz](https://ff-quizzes.netlify.app/web/quiz/33)
Games aren't much fun until you have aliens running around on screen! In this game, we will make use of two types of movements:
## Understanding Game Movement
Games come alive when things start moving around, and there are fundamentally two ways this happens:
- **Keyboard/Mouse movement**: when the user interacts with the keyboard or mouse to move an object on the screen.
- **Game induced movement**: when the game moves an object with a certain time interval.
- **Player-controlled movement**: When you press a key or click your mouse, something moves. This is the direct connection between you and your game world.
- **Automatic movement**: When the game itself decides to move things like those enemy ships that need to patrol the screen whether you're doing anything or not.
So how do we move things on a screen? It's all about cartesian coordinates: we change the location (x,y) of the object and then redraw the screen.
Making objects move on a computer screen is simpler than you might think. Remember those x and y coordinates from math class? That's exactly what we're working with here. When Galileo tracked Jupiter's moons in 1610, he was essentially doing the same thing plotting positions over time to understand motion patterns.
Typically you need the following steps to accomplish *movement* on a screen:
Moving things on screen is like creating a flipbook animation you need to follow these three simple steps:
1. **Set a new location** for an object; this is needed to perceive the object as having moved.
2. **Clear the screen**, the screen needs to be cleared in between draws. We can clear it by drawing a rectangle that we fill with a background color.
3. **Redraw object** at new location. By doing this we finally accomplish moving the object from one location to the other.
1. **Update the position** Change where your object should be (maybe move it 5 pixels to the right)
2. **Erase the old frame** Clear the screen so you don't see ghostly trails everywhere
3. **Draw the new frame** Put your object in its new spot
Here's what it can look like in code:
Do this fast enough, and boom! You've got smooth movement that feels natural to players.
Here's what it can look like in code:
```javascript
//set the hero's location
// Set the hero's location
hero.x += 5;
// clear the rectangle that hosts the hero
// Clear the rectangle that hosts the hero
ctx.clearRect(0, 0, canvas.width, canvas.height);
// redraw the game background and hero
ctx.fillRect(0, 0, canvas.width, canvas.height)
// Redraw the game background and hero
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.drawImage(heroImg, hero.x, hero.y);
```
**Here's what this code does:**
- **Updates** the hero's x-coordinate by 5 pixels to move it horizontally
- **Clears** the entire canvas area to remove the previous frame
- **Fills** the canvas with a black background color
- **Redraws** the hero image at its new position
✅ Can you think of a reason why redrawing your hero many frames per second might accrue performance costs? Read about [alternatives to this pattern](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Optimizing_canvas).
## Handle keyboard events
You handle events by attaching specific events to code. Keyboard events are triggered on the whole window whereas mouse events like a `click` can be connected to clicking a specific element. We will use keyboard events throughout this project.
This is where we connect player input to game action. When someone hits the spacebar to fire a laser or taps an arrow key to dodge an asteroid, your game needs to detect and respond to that input.
Keyboard events happen at the window level, meaning your entire browser window is listening for those keypresses. Mouse clicks, on the other hand, can be tied to specific elements (like clicking a button). For our space game, we'll focus on keyboard controls since that's what gives players that classic arcade feel.
To handle an event you need to use the window's `addEventListener()` method and provide it with two input parameters. The first parameter is the name of the event, for example `keyup`. The second parameter is the function that should be invoked as a result of the event taking place.
This reminds me of how telegraph operators in the 1800s had to translate morse code input into meaningful messages we're doing something similar, translating keypresses into game commands.
To handle an event you need to use the window's `addEventListener()` method and provide it with two input parameters. The first parameter is the name of the event, for example `keyup`. The second parameter is the function that should be invoked as a result of the event taking place.
Here's an example:
```javascript
window.addEventListener('keyup', (evt) => {
// `evt.key` = string representation of the key
// evt.key = string representation of the key
if (evt.key === 'ArrowUp') {
// do something
}
})
});
```
**Breaking down what happens here:**
- **Listens** for keyboard events on the entire window
- **Captures** the event object which contains information about which key was pressed
- **Checks** if the pressed key matches a specific key (in this case, the up arrow)
- **Executes** code when the condition is met
For key events there are two properties on the event you can use to see what key was pressed:
- `key`, this is a string representation of the pressed key, for example `ArrowUp`
- `keyCode`, this is a number representation, for example `37`, corresponds to `ArrowLeft`.
- `key` - this is a string representation of the pressed key, for example `'ArrowUp'`
- `keyCode` - this is a number representation, for example `37`, corresponds to `ArrowLeft`
✅ Key event manipulation is useful outside of game development. What other uses can you think of for this technique?
### Special keys: a caveat
### Special keys: a heads up!
There are some *special* keys that affect the window. That means that if you are listening to a `keyup` event and you use these special keys to move your hero it will also perform horizontal scrolling. For that reason you might want to *shut-off* this built-in browser behavior as you build out your game. You need code like this:
Some keys have built-in browser behaviors that can interfere with your game. Arrow keys scroll the page and spacebar jumps down behaviors you don't want when someone is trying to pilot their spaceship.
We can prevent these default behaviors and let our game handle the input instead. This is similar to how early computer programmers had to override system interrupts to create custom behaviors we're just doing it at the browser level. Here's how:
```javascript
let onKeyDown = function (e) {
const onKeyDown = function (e) {
console.log(e.keyCode);
switch (e.keyCode) {
case 37:
@ -79,27 +109,43 @@ let onKeyDown = function (e) {
window.addEventListener('keydown', onKeyDown);
```
The above code will ensure that arrow-keys and the space key have their *default* behavior shut off. The *shut-off* mechanism happens when we call `e.preventDefault()`.
**Understanding this prevention code:**
- **Checks** for specific key codes that might cause unwanted browser behavior
- **Prevents** the default browser action for arrow keys and spacebar
- **Allows** other keys to function normally
- **Uses** `e.preventDefault()` to stop the browser's built-in behavior
## Game induced movement
We can make things move by themselves by using timers such as the `setTimeout()` or `setInterval()` function that update the location of the object on each tick, or time interval. Here's what that can look like:
Now let's talk about objects that move without player input. Think about enemy ships cruising across the screen, bullets flying in straight lines, or clouds drifting in the background. This autonomous movement makes your game world feel alive even when nobody's touching the controls.
We use JavaScript's built-in timers to update positions at regular intervals. This concept is similar to how pendulum clocks work a regular mechanism that triggers consistent, timed actions. Here's how simple it can be:
```javascript
let id = setInterval(() => {
//move the enemy on the y axis
const id = setInterval(() => {
// Move the enemy on the y axis
enemy.y += 10;
})
}, 100);
```
**Here's what this movement code does:**
- **Creates** a timer that runs every 100 milliseconds
- **Updates** the enemy's y-coordinate by 10 pixels each time
- **Stores** the interval ID so we can stop it later if needed
- **Moves** the enemy downward on the screen automatically
## The game loop
The game loop is a concept that is essentially a function that is invoked at regular intervals. It's called the game loop as everything that should be visible to the user is drawn into the loop. The game loop makes use of all the game objects that are part of the game, drawing all of them unless for some reason shouldn't be part of the game any more. For example if an object is an enemy that was hit by a laser and blows up, it's no longer part of the current game loop (you'll learn more on this in subsequent lessons).
Here's the concept that ties everything together the game loop. If your game were a movie, the game loop would be the film projector, showing frame after frame so fast that everything appears to move smoothly.
Every game has one of these loops running behind the scenes. It's a function that updates all game objects, redraws the screen, and repeats this process continuously. This keeps track of your hero, all the enemies, any lasers flying around the entire game state.
This concept reminds me of how early film animators like Walt Disney had to redraw characters frame by frame to create the illusion of movement. We're doing the same thing, just with code instead of pencils.
Here's what a game loop can typically look like, expressed in code:
```javascript
let gameLoopId = setInterval(() =>
const gameLoopId = setInterval(() => {
function gameLoop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
@ -107,17 +153,29 @@ let gameLoopId = setInterval(() =>
drawHero();
drawEnemies();
drawStaticObjects();
}
gameLoop();
}, 200);
```
The above loop is invoked every `200` milliseconds to redraw the canvas. You have the ability to choose the best interval that makes sense for your game.
**Understanding the game loop structure:**
- **Clears** the entire canvas to remove the previous frame
- **Fills** the background with a solid color
- **Draws** all game objects in their current positions
- **Repeats** this process every 200 milliseconds to create smooth animation
- **Manages** the frame rate by controlling the interval timing
## Continuing the Space Game
You will take the existing code and extend it. Either start with the code that you completed during part I or use the code in [Part II- starter](your-work).
Now we'll add movement to the static scene you built previously. We're going to transform it from a screenshot into an interactive experience. We'll work through this step by step to ensure each piece builds on the last.
Grab the code from where we left off in the previous lesson (or start with the code in the [Part II- starter](your-work) folder if you need a fresh start).
**Here's what we're building today:**
- **Hero controls**: Arrow keys will pilot your spaceship around the screen
- **Enemy movement**: Those alien ships will start their advance
- **Moving the hero**: you will add code to ensure you can move the hero using the arrow keys.
- **Move enemies**: you will also need to add code to ensure the enemies move from top to bottom at a given rate.
Let's begin implementing these features.
## Recommended steps
@ -132,25 +190,29 @@ Locate the files that have been created for you in the `your-work` sub folder. I
-| package.json
```
You start your project the `your_work` folder by typing:
You start your project in the `your-work` folder by typing:
```bash
cd your-work
npm start
```
**What this command does:**
- **Navigates** to your project directory
- **Starts** a HTTP Server on address `http://localhost:5000`
- **Serves** your game files so you can test them in a browser
The above will start a HTTP Server on address `http://localhost:5000`. Open up a browser and input that address, right now it should render the hero and all the enemies; nothing is moving - yet!
### Add code
1. **Add dedicated objects** for `hero` and `enemy` and `game object`, they should have `x` and `y` properties. (Remember the portion on [Inheritance or composition](../README.md) ).
1. **Add dedicated objects** for `hero` and `enemy` and `game object`, they should have `x` and `y` properties. (Remember the portion on [Inheritance or composition](../README.md)).
*HINT* `game object` should be the one with `x` and `y` and the ability to draw itself to a canvas.
>tip: start by adding a new GameObject class with its constructor delineated as below, and then draw it to the canvas:
> **Tip**: Start by adding a new `GameObject` class with its constructor delineated as below, and then draw it to the canvas:
```javascript
class GameObject {
constructor(x, y) {
this.x = x;
@ -168,12 +230,22 @@ The above will start a HTTP Server on address `http://localhost:5000`. Open up a
}
```
Now, extend this GameObject to create the Hero and Enemy.
**Understanding this base class:**
- **Defines** common properties that all game objects share (position, size, image)
- **Includes** a `dead` flag to track whether the object should be removed
- **Provides** a `draw()` method that renders the object on the canvas
- **Sets** default values for all properties that child classes can override
Now, extend this `GameObject` to create the `Hero` and `Enemy`:
```javascript
class Hero extends GameObject {
constructor(x, y) {
...it needs an x, y, type, and speed
super(x, y);
this.width = 98;
this.height = 75;
this.type = "Hero";
this.speed = 5;
}
}
```
@ -182,129 +254,152 @@ The above will start a HTTP Server on address `http://localhost:5000`. Open up a
class Enemy extends GameObject {
constructor(x, y) {
super(x, y);
(this.width = 98), (this.height = 50);
this.width = 98;
this.height = 50;
this.type = "Enemy";
let id = setInterval(() => {
const id = setInterval(() => {
if (this.y < canvas.height - this.height) {
this.y += 5;
} else {
console.log('Stopped at', this.y)
console.log('Stopped at', this.y);
clearInterval(id);
}
}, 300)
}, 300);
}
}
```
**Key concepts in these classes:**
- **Inherits** from `GameObject` using the `extends` keyword
- **Calls** the parent constructor with `super(x, y)`
- **Sets** specific dimensions and properties for each object type
- **Implements** automatic movement for enemies using `setInterval()`
2. **Add key-event handlers** to handle key navigation (move hero up/down left/right)
*REMEMBER* it's a cartesian system, top-left is `0,0`. Also remember to add code to stop *default behavior*
>tip: create your onKeyDown function and attach it to the window:
> **Tip**: Create your `onKeyDown` function and attach it to the window:
```javascript
let onKeyDown = function (e) {
console.log(e.keyCode);
...add the code from the lesson above to stop default behavior
}
};
window.addEventListener("keydown", onKeyDown);
const onKeyDown = function (e) {
console.log(e.keyCode);
// Add the code from the lesson above to stop default behavior
switch (e.keyCode) {
case 37:
case 39:
case 38:
case 40: // Arrow keys
case 32:
e.preventDefault();
break; // Space
default:
break; // do not block other keys
}
};
window.addEventListener("keydown", onKeyDown);
```
**What this event handler does:**
- **Listens** for keydown events on the entire window
- **Logs** the key code to help you debug which keys are being pressed
- **Prevents** default browser behavior for arrow keys and spacebar
- **Allows** other keys to function normally
Check your browser console at this point, and watch the keystrokes being logged.
3. **Implement** the [Pub sub pattern](../README.md), this will keep your code clean as you follow the remaining parts.
The Publish-Subscribe pattern helps organize your code by separating event detection from event handling. This makes your code more modular and easier to maintain.
To do this last part, you can:
1. **Add an event listener** on the window:
```javascript
window.addEventListener("keyup", (evt) => {
if (evt.key === "ArrowUp") {
eventEmitter.emit(Messages.KEY_EVENT_UP);
} else if (evt.key === "ArrowDown") {
eventEmitter.emit(Messages.KEY_EVENT_DOWN);
} else if (evt.key === "ArrowLeft") {
eventEmitter.emit(Messages.KEY_EVENT_LEFT);
} else if (evt.key === "ArrowRight") {
eventEmitter.emit(Messages.KEY_EVENT_RIGHT);
}
});
```
1. **Create an EventEmitter class** to publish and subscribe to messages:
```javascript
class EventEmitter {
constructor() {
this.listeners = {};
}
on(message, listener) {
if (!this.listeners[message]) {
this.listeners[message] = [];
}
this.listeners[message].push(listener);
}
emit(message, payload = null) {
if (this.listeners[message]) {
this.listeners[message].forEach((l) => l(message, payload));
}
}
}
```
1. **Add constants** and set up the EventEmitter:
```javascript
const Messages = {
KEY_EVENT_UP: "KEY_EVENT_UP",
KEY_EVENT_DOWN: "KEY_EVENT_DOWN",
KEY_EVENT_LEFT: "KEY_EVENT_LEFT",
KEY_EVENT_RIGHT: "KEY_EVENT_RIGHT",
};
let heroImg,
enemyImg,
laserImg,
canvas, ctx,
gameObjects = [],
hero,
eventEmitter = new EventEmitter();
```
1. **Initialize the game**
window.addEventListener("keyup", (evt) => {
if (evt.key === "ArrowUp") {
eventEmitter.emit(Messages.KEY_EVENT_UP);
} else if (evt.key === "ArrowDown") {
eventEmitter.emit(Messages.KEY_EVENT_DOWN);
} else if (evt.key === "ArrowLeft") {
eventEmitter.emit(Messages.KEY_EVENT_LEFT);
} else if (evt.key === "ArrowRight") {
eventEmitter.emit(Messages.KEY_EVENT_RIGHT);
}
});
```
**What this event system does:**
- **Detects** keyboard input and converts it to custom game events
- **Separates** input detection from game logic
- **Makes** it easy to change controls later without affecting game code
- **Allows** multiple systems to respond to the same input
2. **Create an EventEmitter class** to publish and subscribe to messages:
```javascript
function initGame() {
gameObjects = [];
createEnemies();
createHero();
eventEmitter.on(Messages.KEY_EVENT_UP, () => {
hero.y -=5 ;
})
eventEmitter.on(Messages.KEY_EVENT_DOWN, () => {
hero.y += 5;
});
eventEmitter.on(Messages.KEY_EVENT_LEFT, () => {
hero.x -= 5;
});
eventEmitter.on(Messages.KEY_EVENT_RIGHT, () => {
hero.x += 5;
});
}
```
```javascript
class EventEmitter {
constructor() {
this.listeners = {};
}
on(message, listener) {
if (!this.listeners[message]) {
this.listeners[message] = [];
}
this.listeners[message].push(listener);
}
3. **Add constants** and set up the EventEmitter:
1. **Setup the game loop**
```javascript
const Messages = {
KEY_EVENT_UP: "KEY_EVENT_UP",
KEY_EVENT_DOWN: "KEY_EVENT_DOWN",
KEY_EVENT_LEFT: "KEY_EVENT_LEFT",
KEY_EVENT_RIGHT: "KEY_EVENT_RIGHT",
};
let heroImg,
enemyImg,
laserImg,
canvas, ctx,
gameObjects = [],
hero,
eventEmitter = new EventEmitter();
```
**Understanding the setup:**
- **Defines** message constants to avoid typos and make refactoring easier
- **Declares** variables for images, canvas context, and game state
- **Creates** a global event emitter for the pub-sub system
- **Initializes** an array to hold all game objects
4. **Initialize the game**
Refactor the window.onload function to initialize the game and set up a game loop on a good interval. You'll also add a laser beam:
```javascript
function initGame() {
gameObjects = [];
createEnemies();
createHero();
eventEmitter.on(Messages.KEY_EVENT_UP, () => {
hero.y -= 5;
});
eventEmitter.on(Messages.KEY_EVENT_DOWN, () => {
hero.y += 5;
});
eventEmitter.on(Messages.KEY_EVENT_LEFT, () => {
hero.x -= 5;
});
4. **Setup the game loop**
Refactor the `window.onload` function to initialize the game and set up a game loop on a good interval. You'll also add a laser beam:
```javascript
window.onload = async () => {
@ -315,16 +410,22 @@ The above will start a HTTP Server on address `http://localhost:5000`. Open up a
laserImg = await loadTexture("assets/laserRed.png");
initGame();
let gameLoopId = setInterval(() => {
const gameLoopId = setInterval(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawGameObjects(ctx);
}, 100)
}, 100);
};
```
**Understanding the game setup:**
- **Waits** for the page to load completely before starting
- **Gets** the canvas element and its 2D rendering context
- **Loads** all image assets asynchronously using `await`
- **Starts** the game loop running at 100ms intervals (10 FPS)
- **Clears** and redraws the entire screen each frame
5. **Add code** to move enemies at a certain interval
Refactor the `createEnemies()` function to create the enemies and push them into the new gameObjects class:
@ -345,6 +446,48 @@ The above will start a HTTP Server on address `http://localhost:5000`. Open up a
}
}
```
**What the enemy creation does:**
- **Calculates** positions to center enemies on the screen
- **Creates** a grid of enemies using nested loops
- **Assigns** the enemy image to each enemy object
- **Adds** each enemy to the global game objects array
and add a `createHero()` function to do a similar process for the hero.
```javascript
function createHero() {
hero = new Hero(
canvas.width / 2 - 45,
canvas.height - canvas.height / 4
);
hero.img = heroImg;
gameObjects.push(hero);
}
```
**What the hero creation does:**
- **Positions** the hero at the bottom center of the screen
- **Assigns** the hero image to the hero object
- **Adds** the hero to the game objects array for rendering
and finally, add a `drawGameObjects()` function to start the drawing:
```javascript
function drawGameObjects(ctx) {
gameObjects.forEach(go => go.draw(ctx));
}
```
**Understanding the drawing function:**
- **Iterates** through all game objects in the array
- **Calls** the `draw()` method on each object
- **Passes** the canvas context so objects can render themselves
Your enemies should start advancing on your hero spaceship!
}
}
```
and add a `createHero()` function to do a similar process for the hero.
@ -373,15 +516,31 @@ The above will start a HTTP Server on address `http://localhost:5000`. Open up a
## GitHub Copilot Agent Challenge 🚀
Use the Agent mode to complete the following challenge:
Here's a challenge that will improve your game's polish: adding boundaries and smooth controls. Currently, your hero can fly off the screen, and the movement might feel choppy.
**Description:** Enhance the space game by implementing boundary detection and smooth movement controls. This challenge will help you practice game object manipulation, event handling, and canvas rendering techniques.
**Your Mission:** Make your spaceship feel more realistic by implementing screen boundaries and fluid movement. This is similar to how NASA's flight control systems prevent spacecraft from exceeding safe operational parameters.
**Prompt:** Create a boundary detection system that prevents the hero spaceship from moving outside the canvas boundaries. Additionally, implement a smooth movement system where holding down arrow keys creates continuous movement instead of single-step movement. Make sure the hero stops immediately when keys are released and add visual feedback when the hero hits a boundary (like a brief color change or glow effect).
**Here's what to build:** Create a system that keeps your hero spaceship on screen, and make the controls feel smooth. When players hold down an arrow key, the ship should glide continuously rather than moving in discrete steps. Consider adding visual feedback when the ship reaches screen boundaries perhaps a subtle effect to indicate the edge of the play area.
Learn more about [agent mode](https://code.visualstudio.com/blogs/2025/02/24/introducing-copilot-agent-mode) here.
## 🚀 Challenge
As you can see, your code can turn into 'spaghetti code' when you start adding functions and variables and classes. How can you better organize your code so that it is more readable? Sketch out a system to organize your code, even if it still resides in one file.
Code organization becomes increasingly important as projects grow. You might have noticed your file getting crowded with functions, variables, and classes all mixed together. This reminds me of how the engineers organizing the Apollo mission code had to create clear, maintainable systems that multiple teams could work on simultaneously.
**Your mission:**
Think like a software architect. How would you organize your code so that six months from now, you (or a teammate) could understand what's happening? Even if everything stays in one file for now, you can create better organization:
- **Grouping related functions** together with clear comment headers
- **Separating concerns** - keep game logic separate from rendering
- **Using consistent naming** conventions for variables and functions
- **Creating modules** or namespaces to organize different aspects of your game
- **Adding documentation** that explains the purpose of each major section
**Reflection questions:**
- Which parts of your code are hardest to understand when you come back to them?
- How could you organize your code to make it easier for someone else to contribute?
- What would happen if you wanted to add new features like power-ups or different enemy types?
## Post-Lecture Quiz
@ -389,7 +548,15 @@ As you can see, your code can turn into 'spaghetti code' when you start adding f
## Review & Self Study
While we're writing our game without using frameworks, there are many JavaScript-based canvas frameworks for game development. Take some time to do some [reading about these](https://github.com/collections/javascript-game-engines).
We've been building everything from scratch, which is fantastic for learning, but here's a little secret there are some amazing JavaScript frameworks out there that can handle a lot of the heavy lifting for you. Once you feel comfortable with the fundamentals we've covered, it's worth [exploring what's available](https://github.com/collections/javascript-game-engines).
Think of frameworks like having a well-stocked toolbox instead of making every tool by hand. They can solve many of those code organization challenges we talked about, plus offer features that would take weeks to build yourself.
**Things worth exploring:**
- How game engines organize code you'll be amazed at the clever patterns they use
- Performance tricks for making canvas games run butter-smooth
- Modern JavaScript features that can make your code cleaner and more maintainable
- Different approaches to managing game objects and their relationships
## Assignment

@ -2,10 +2,21 @@
## Instructions
Go over your current /app.js file in your game folder, and find ways to comment it and tidy it up. It's very easy for code to get out of control, and now's a good chance to add comments to ensure that you have readable code so that you can use it later.
Clean, well-documented code is essential for maintaining and sharing your projects. In this assignment, you'll practice one of the most important habits of professional developers: writing clear, helpful comments that explain your code's purpose and functionality.
Go over your current `app.js` file in your game folder, and find ways to comment it and tidy it up. It's very easy for code to get out of control, and now's a good chance to add comments to ensure that you have readable code so that you can use it later.
**Your task includes:**
- **Add comments** explaining what each major section of code does
- **Document functions** with clear descriptions of their purpose and parameters
- **Organize code** into logical blocks with section headers
- **Remove** any unused or redundant code
- **Use consistent** naming conventions for variables and functions
## Rubric
| Criteria | Exemplary | Adequate | Needs Improvement |
| -------- | ------------------------------------------------------------------ | ------------------------------------- | -------------------------------------------------------------- |
| | `app.js` code is fully commented and organized into logical blocks | `app.js` code is adequately commented | `app.js` code is somewhat disorganized and lacks good comments |
| Criteria | Exemplary | Adequate | Needs Improvement |
| -------- | --------- | -------- | ----------------- |
| **Code Documentation** | `app.js` code is fully commented with clear, helpful explanations for all major sections and functions | `app.js` code is adequately commented with basic explanations for most sections | `app.js` code has minimal comments and lacks clear explanations |
| **Code Organization** | Code is organized into logical blocks with clear section headers and consistent structure | Code has some organization with basic grouping of related functionality | Code is somewhat disorganized and difficult to follow |
| **Code Quality** | All variables and functions use descriptive names, no unused code, follows consistent conventions | Most code follows good naming practices with minimal unused code | Variable names are unclear, contains unused code, inconsistent style |

@ -4,77 +4,107 @@
[Pre-lecture quiz](https://ff-quizzes.netlify.app/web/quiz/35)
In this lesson you will learn how to shoot lasers with JavaScript! We will add two things to our game:
Think about the moment in Star Wars when Luke's proton torpedoes hit the Death Star's exhaust port. That precise collision detection changed the fate of the galaxy! In games, collision detection works the same way - it determines when objects interact and what happens next.
- **A laser**: this laser is shot from your heroes ship and vertically upwards
- **Collision detection**, as part of implementing the ability to *shoot* we will also add some nice game rules:
- **Laser hits enemy**: Enemy dies if hit by a laser
- **Laser hits top screen**: A laser is destroyed if hitting the top part of the screen
- **Enemy and hero collision**: An enemy and the hero are destroyed if hitting each other
- **Enemy hits bottom of the screen**: An enemy and a hero are destroyed if the enemy hits the bottom of the screen
In this lesson, you'll add laser weapons to your space game and implement collision detection. Just like NASA's mission planners calculate spacecraft trajectories to avoid debris, you'll learn to detect when game objects intersect. We'll break this down into manageable steps that build on each other.
In short, you -- *the hero* -- need to hit all enemies with a laser before they manage to move to the bottom of the screen.
By the end, you'll have a functioning combat system where lasers destroy enemies and collisions trigger game events. These same collision principles are used in everything from physics simulations to interactive web interfaces.
✅ Do a little research on the very first computer game ever written. What was its functionality?
Let's be heroic together!
## Collision detection
How do we do collision detection? We need to think of our game objects as rectangles moving about. Why is that you might ask? Well, the image used to draw a game object is a rectangle: it has an `x`, `y`, `width` and `height`.
Collision detection works like the proximity sensors on the Apollo lunar module - it constantly checks distances and triggers alerts when objects get too close. In games, this system determines when objects interact and what should happen next.
If two rectangles, i.e a hero and enemy *intersect*, you have a collision. What should happen then is up to the rules of the game. To implement collision detection you therefore need the following:
The approach we'll use treats every game object as a rectangle, similar to how air traffic control systems use simplified geometric shapes to track aircraft. This rectangular method might seem basic, but it's computationally efficient and works well for most game scenarios.
1. A way to get a rectangle representation of a game object, something like this:
### Rectangle representation
```javascript
rectFromGameObject() {
return {
top: this.y,
left: this.x,
bottom: this.y + this.height,
right: this.x + this.width
}
}
```
Every game object needs coordinate boundaries, similar to how the Mars Pathfinder rover mapped its location on the Martian surface. Here's how we define these boundary coordinates:
2. A comparison function, this function can look like this:
```javascript
rectFromGameObject() {
return {
top: this.y,
left: this.x,
bottom: this.y + this.height,
right: this.x + this.width
}
}
```
```javascript
function intersectRect(r1, r2) {
return !(r2.left > r1.right ||
r2.right < r1.left ||
r2.top > r1.bottom ||
r2.bottom < r1.top);
}
```
**Let's break this down:**
- **Top edge**: That's just where your object starts vertically (its y position)
- **Left edge**: Where it starts horizontally (its x position)
- **Bottom edge**: Add the height to the y position - now you know where it ends!
- **Right edge**: Add the width to the x position - and you've got the complete boundary
## How do we destroy things
### Intersection algorithm
To destroy things in a game you need to let the game know it should no longer paint this item in the game loop that triggers on a certain interval. A way to do this is to mark a game object as *dead* when something happens, like so:
Detecting rectangle intersections uses logic similar to how the Hubble Space Telescope determines if celestial objects are overlapping in its field of view. The algorithm checks for separation:
```javascript
// collision happened
enemy.dead = true
function intersectRect(r1, r2) {
return !(r2.left > r1.right ||
r2.right < r1.left ||
r2.top > r1.bottom ||
r2.bottom < r1.top);
}
```
Then you an proceed to sort out *dead* objects before repainting the screen, like so:
**The separation test works like radar systems:**
- Is rectangle 2 completely to the right of rectangle 1?
- Is rectangle 2 completely to the left of rectangle 1?
- Is rectangle 2 completely below rectangle 1?
- Is rectangle 2 completely above rectangle 1?
If none of these conditions are true, the rectangles must be overlapping. This approach mirrors how radar operators determine if two aircraft are at safe distances.
## Managing object lifecycles
When a laser hits an enemy, both objects need to be removed from the game. However, deleting objects mid-loop can cause crashes - a lesson learned the hard way in early computer systems like the Apollo Guidance Computer. Instead, we use a "mark for deletion" approach that safely removes objects between frames.
Here's how we mark something for removal:
```javascript
gameObjects = gameObject.filter(go => !go.dead);
// Mark object for removal
enemy.dead = true;
```
## How do we fire a laser
**Why this approach works:**
- We mark the object as "dead" but don't delete it right away
- This lets the current game frame finish safely
- No crashes from trying to use something that's already gone!
Firing a laser translates to responding to a key-event and creating an object that moves in a certain direction. We therefore need to carry out the following steps:
Then filter out marked objects before the next render cycle:
```javascript
gameObjects = gameObjects.filter(go => !go.dead);
```
1. **Create a laser object**: from the top of our hero's ship, that upon creation starts moving upwards towards the screen top.
2. **Attach code to a key event**: we need to choose a key on the keyboard that represents the player shooting the laser.
3. **Create a game object that looks like a laser** when the key is pressed.
**What this filtering does:**
- Creates a fresh list with only the "living" objects
- Tosses out anything marked as dead
- Keeps your game running smoothly
- Prevents memory bloat from accumulating destroyed objects
## Cooldown on our laser
## Implementing laser mechanics
The laser needs to fire every time you press a key, like *space* for example. To prevent the game producing way too many lasers in a short time we need to fix this. The fix is by implementing a so called *cooldown*, a timer, that ensures that a laser can only be fired so often. You can implement that in the following way:
Laser projectiles in games work on the same principle as photon torpedoes in Star Trek - they're discrete objects that travel in straight lines until they hit something. Each spacebar press creates a new laser object that moves across the screen.
To make this work, we need to coordinate a few different pieces:
**Key components to implement:**
- **Create** laser objects that spawn from the hero's position
- **Handle** keyboard input to trigger laser creation
- **Manage** laser movement and lifecycle
- **Implement** visual representation for the laser projectiles
## Implementing firing rate control
Unlimited firing rates would overwhelm the game engine and make gameplay too easy. Real weapon systems face similar constraints - even the USS Enterprise's phasers needed time to recharge between shots.
We'll implement a cooldown system that prevents rapid-fire spamming while maintaining responsive controls:
```javascript
class Cooldown {
@ -82,41 +112,55 @@ class Cooldown {
this.cool = false;
setTimeout(() => {
this.cool = true;
}, time)
}, time);
}
}
class Weapon {
constructor {
constructor() {
this.cooldown = null;
}
fire() {
if (!this.cooldown || this.cooldown.cool) {
// produce a laser
// Create laser projectile
this.cooldown = new Cooldown(500);
} else {
// do nothing - it hasn't cooled down yet.
// Weapon is still cooling down
}
}
}
```
✅ Refer to lesson 1 in the space game series to remind yourself about *cooldowns*.
**How the cooldown works:**
- When created, the weapon starts "hot" (can't fire yet)
- After the timeout period, it becomes "cool" (ready to fire)
- Before firing, we check: "Is the weapon cool?"
- This prevents spam-clicking while keeping controls responsive
✅ Refer to lesson 1 in the space game series to remind yourself about cooldowns.
## What to build
## Building the collision system
You will take the existing code (which you should have cleaned up and refactored) from the previous lesson, and extend it. Either start with the code from part II or use the code at [Part III- starter](/your-work).
You'll extend your existing space game code to create a collision detection system. Like the International Space Station's automated collision avoidance system, your game will continuously monitor object positions and respond to intersections.
> tip: the laser that you'll work with is already in your assets folder and referenced by your code
Starting from your previous lesson's code, you'll add collision detection with specific rules that govern object interactions.
- **Add collision detection**, when a laser collides with something the following rules should apply:
1. **Laser hits enemy**: enemy dies if hit by a laser
2. **Laser hits top screen**: A laser is destroyed if it hits the top part of our screen
3. **Enemy and hero collision**: an enemy and the hero is destroyed if hitting each other
4. **Enemy hits bottom of the screen**: An enemy and a hero is destroyed if the enemy hits the bottom of the screen
> 💡 **Pro Tip**: The laser sprite is already included in your assets folder and referenced in your code, ready for implementation.
## Recommended steps
### Collision rules to implement
Locate the files that have been created for you in the `your-work` sub folder. It should contain the following:
**Game mechanics to add:**
1. **Laser hits enemy**: Enemy object is destroyed when struck by a laser projectile
2. **Laser hits screen boundary**: Laser is removed when reaching the top edge of the screen
3. **Enemy and hero collision**: Both objects are destroyed when they intersect
4. **Enemy reaches bottom**: Game over condition when enemies reach the screen bottom
## Setting up your development environment
Good news - we've already set up most of the groundwork for you! All your game assets and basic structure are waiting in the `your-work` subfolder, ready for you to add the cool collision features.
### Project structure
```bash
-| assets
@ -128,155 +172,255 @@ Locate the files that have been created for you in the `your-work` sub folder. I
-| package.json
```
You start your project the `your_work` folder by typing:
**Understanding the file structure:**
- **Contains** all sprite images needed for the game objects
- **Includes** the main HTML document and JavaScript application file
- **Provides** package configuration for local development server
### Starting the development server
Navigate to your project folder and start the local server:
```bash
cd your-work
npm start
```
The above will start a HTTP Server on address `http://localhost:5000`. Open up a browser and input that address, right now it should render the hero and all the enemies, nothing is moving - yet :).
**This command sequence:**
- **Changes** directory to your working project folder
- **Starts** a local HTTP server on `http://localhost:5000`
- **Serves** your game files for testing and development
- **Enables** live development with automatic reloading
Open your browser and navigate to `http://localhost:5000` to see your current game state with the hero and enemies rendered on screen.
### Step-by-step implementation
Like the systematic approach NASA used to program the Voyager spacecraft, we'll implement collision detection methodically, building each component step by step.
#### 1. Add rectangle collision bounds
First, let's teach our game objects how to describe their boundaries. Add this method to your `GameObject` class:
```javascript
rectFromGameObject() {
return {
top: this.y,
left: this.x,
bottom: this.y + this.height,
right: this.x + this.width,
};
}
```
**This method accomplishes:**
- **Creates** a rectangle object with precise boundary coordinates
- **Calculates** bottom and right edges using position plus dimensions
- **Returns** an object ready for collision detection algorithms
- **Provides** a standardized interface for all game objects
#### 2. Implement intersection detection
Now let's create our collision detective - a function that can tell when two rectangles are overlapping:
```javascript
function intersectRect(r1, r2) {
return !(
r2.left > r1.right ||
r2.right < r1.left ||
r2.top > r1.bottom ||
r2.bottom < r1.top
);
}
```
**This algorithm works by:**
- **Tests** four separation conditions between rectangles
- **Returns** `false` if any separation condition is true
- **Indicates** collision when no separation exists
- **Uses** negation logic for efficient intersection testing
#### 3. Implement laser firing system
Here's where things get exciting! Let's set up the laser firing system.
##### Message constants
First, let's define some message types so different parts of our game can talk to each other:
```javascript
KEY_EVENT_SPACE: "KEY_EVENT_SPACE",
COLLISION_ENEMY_LASER: "COLLISION_ENEMY_LASER",
COLLISION_ENEMY_HERO: "COLLISION_ENEMY_HERO",
```
**These constants provide:**
- **Standardizes** event names throughout the application
- **Enables** consistent communication between game systems
- **Prevents** typos in event handler registration
### Add code
##### Keyboard input handling
1. **Setup a rectangle representation of your game object, to handle collision** The below code allows you to get a rectangle representation of a `GameObject`. Edit your GameObject class to extend it:
Add space key detection to your key event listener:
```javascript
rectFromGameObject() {
return {
top: this.y,
left: this.x,
bottom: this.y + this.height,
right: this.x + this.width,
};
```javascript
} else if(evt.keyCode === 32) {
eventEmitter.emit(Messages.KEY_EVENT_SPACE);
}
```
**This input handler:**
- **Detects** space key presses using keyCode 32
- **Emits** a standardized event message
- **Enables** decoupled firing logic
##### Event listener setup
Register firing behavior in your `initGame()` function:
```javascript
eventEmitter.on(Messages.KEY_EVENT_SPACE, () => {
if (hero.canFire()) {
hero.fire();
}
});
```
**This event listener:**
- **Responds** to space key events
- **Checks** firing cooldown status
- **Triggers** laser creation when allowed
Add collision handling for laser-enemy interactions:
```javascript
eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => {
first.dead = true;
second.dead = true;
});
```
**This collision handler:**
- **Receives** collision event data with both objects
- **Marks** both objects for removal
- **Ensures** proper cleanup after collision
#### 4. Create the Laser class
Implement a laser projectile that moves upward and manages its own lifecycle:
```javascript
class Laser extends GameObject {
constructor(x, y) {
super(x, y);
this.width = 9;
this.height = 33;
this.type = 'Laser';
this.img = laserImg;
let id = setInterval(() => {
if (this.y > 0) {
this.y -= 15;
} else {
this.dead = true;
clearInterval(id);
}
```
2. **Add code that checks collision** This will be a new function that tests whether two rectangles intersect:
```javascript
function intersectRect(r1, r2) {
return !(
r2.left > r1.right ||
r2.right < r1.left ||
r2.top > r1.bottom ||
r2.bottom < r1.top
);
}
```
3. **Add laser firing capability**
1. **Add key-event message**. The *space* key should create a laser just above the hero ship. Add three constants in the Messages object:
```javascript
KEY_EVENT_SPACE: "KEY_EVENT_SPACE",
COLLISION_ENEMY_LASER: "COLLISION_ENEMY_LASER",
COLLISION_ENEMY_HERO: "COLLISION_ENEMY_HERO",
```
1. **Handle space key**. Edit the `window.addEventListener` keyup function to handle spaces:
```javascript
} else if(evt.keyCode === 32) {
eventEmitter.emit(Messages.KEY_EVENT_SPACE);
}
```
1. **Add listeners**. Edit the `initGame()` function to ensure that hero can fire when the space bar is hit:
```javascript
eventEmitter.on(Messages.KEY_EVENT_SPACE, () => {
if (hero.canFire()) {
hero.fire();
}
```
and add a new `eventEmitter.on()` function to ensure behavior when an enemy collides with a laser:
```javascript
eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => {
first.dead = true;
second.dead = true;
})
```
1. **Move object**, Ensure the laser moves to the top of the screen gradually. You'll create a new Laser class that extends `GameObject`, as you've done before:
```javascript
class Laser extends GameObject {
constructor(x, y) {
super(x,y);
(this.width = 9), (this.height = 33);
this.type = 'Laser';
this.img = laserImg;
let id = setInterval(() => {
if (this.y > 0) {
this.y -= 15;
} else {
this.dead = true;
clearInterval(id);
}
}, 100)
}
}, 100);
}
}
```
**This class implementation:**
- **Extends** GameObject to inherit basic functionality
- **Sets** appropriate dimensions for the laser sprite
- **Creates** automatic upward movement using `setInterval()`
- **Handles** self-destruction when reaching screen top
- **Manages** its own animation timing and cleanup
#### 5. Implement collision detection system
Create a comprehensive collision detection function:
```javascript
function updateGameObjects() {
const enemies = gameObjects.filter(go => go.type === 'Enemy');
const lasers = gameObjects.filter(go => go.type === "Laser");
// Test laser-enemy collisions
lasers.forEach((laser) => {
enemies.forEach((enemy) => {
if (intersectRect(laser.rectFromGameObject(), enemy.rectFromGameObject())) {
eventEmitter.emit(Messages.COLLISION_ENEMY_LASER, {
first: laser,
second: enemy,
});
}
```
1. **Handle collisions**, Implement collision rules for the laser. Add an `updateGameObjects()` function that tests colliding objects for hits
```javascript
function updateGameObjects() {
const enemies = gameObjects.filter(go => go.type === 'Enemy');
const lasers = gameObjects.filter((go) => go.type === "Laser");
// laser hit something
lasers.forEach((l) => {
enemies.forEach((m) => {
if (intersectRect(l.rectFromGameObject(), m.rectFromGameObject())) {
eventEmitter.emit(Messages.COLLISION_ENEMY_LASER, {
first: l,
second: m,
});
}
});
});
gameObjects = gameObjects.filter(go => !go.dead);
}
```
Make sure to add `updateGameObjects()` into your game loop in `window.onload`.
4. **Implement cooldown** on the laser, so it can only be fired so often.
Finally, edit the Hero class so that it can cooldown:
```javascript
class Hero extends GameObject {
constructor(x, y) {
super(x, y);
(this.width = 99), (this.height = 75);
this.type = "Hero";
this.speed = { x: 0, y: 0 };
this.cooldown = 0;
}
fire() {
gameObjects.push(new Laser(this.x + 45, this.y - 10));
this.cooldown = 500;
let id = setInterval(() => {
if (this.cooldown > 0) {
this.cooldown -= 100;
} else {
clearInterval(id);
}
}, 200);
}
canFire() {
return this.cooldown === 0;
}
});
});
// Remove destroyed objects
gameObjects = gameObjects.filter(go => !go.dead);
}
```
**This collision system:**
- **Filters** game objects by type for efficient testing
- **Tests** every laser against every enemy for intersections
- **Emits** collision events when intersections are detected
- **Cleans** up destroyed objects after collision processing
> ⚠️ **Important**: Add `updateGameObjects()` to your main game loop in `window.onload` to enable collision detection.
#### 6. Add cooldown system to Hero class
Enhance the Hero class with firing mechanics and rate limiting:
```javascript
class Hero extends GameObject {
constructor(x, y) {
super(x, y);
this.width = 99;
this.height = 75;
this.type = "Hero";
this.speed = { x: 0, y: 0 };
this.cooldown = 0;
}
fire() {
gameObjects.push(new Laser(this.x + 45, this.y - 10));
this.cooldown = 500;
let id = setInterval(() => {
if (this.cooldown > 0) {
this.cooldown -= 100;
} else {
clearInterval(id);
}
```
}, 200);
}
canFire() {
return this.cooldown === 0;
}
}
```
**Understanding the enhanced Hero class:**
- **Initializes** cooldown timer at zero (ready to fire)
- **Creates** laser objects positioned above the hero ship
- **Sets** cooldown period to prevent rapid firing
- **Decrements** cooldown timer using interval-based updates
- **Provides** firing status check through `canFire()` method
### Testing your implementation
Your space game now features complete collision detection and combat mechanics. 🚀 Test these new capabilities:
- **Navigate** with arrow keys to verify movement controls
- **Fire lasers** with the spacebar - notice how the cooldown prevents spam-clicking
- **Observe collisions** when lasers hit enemies, triggering removal
- **Verify cleanup** as destroyed objects disappear from the game
At this point, your game has some functionality! You can navigate with your arrow keys, fire a laser with your space bar, and enemies disappear when you hit them. Well done!
You've successfully implemented a collision detection system using the same mathematical principles that guide spacecraft navigation and robotics.
## GitHub Copilot Agent Challenge 🚀

@ -2,10 +2,47 @@
## Instructions
To better understand how collisions work, build a very small game with a few items that collide. Make them move via keypresses or mouse clicks, and make something happen to one of the items when it is hit. It could be something like a meteor hitting the earth, or bumper-cars. Get creative!
Apply your collision detection knowledge by creating a custom mini-game that demonstrates different types of object interactions. This assignment will help you understand collision mechanics through creative implementation and experimentation.
### Project requirements
**Create a small interactive game featuring:**
- **Multiple moving objects** that can be controlled via keyboard or mouse input
- **Collision detection system** using rectangle intersection principles from the lesson
- **Visual feedback** when collisions occur (object destruction, color changes, effects)
- **Game rules** that make collisions meaningful and engaging
### Creative suggestions
**Consider implementing one of these scenarios:**
- **Asteroid field**: Navigate a ship through dangerous space debris
- **Bumper cars**: Create a physics-based collision arena
- **Meteor defense**: Protect Earth from incoming space rocks
- **Collection game**: Gather items while avoiding obstacles
- **Territory control**: Competing objects trying to claim space
### Technical implementation
**Your solution should demonstrate:**
- Proper use of rectangle-based collision detection
- Event-driven programming for user input
- Object lifecycle management (creation and destruction)
- Clean code organization with appropriate class structure
### Bonus challenges
**Enhance your game with additional features:**
- **Particle effects** when collisions occur
- **Sound effects** for different collision types
- **Scoring system** based on collision outcomes
- **Multiple collision types** with different behaviors
- **Progressive difficulty** that increases over time
## Rubric
| Criteria | Exemplary | Adequate | Needs Improvement |
| -------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------ | ----------------- |
| | Complete working code sample is produced, with items drawn to canvas, basic collision happening, and reactions occurring | Code is incomplete in some way | Code malfunctions |
| Criteria | Exemplary | Adequate | Needs Improvement |
|----------|-----------|----------|-------------------|
| **Collision Detection** | Implements accurate rectangle-based collision detection with multiple object types and sophisticated interaction rules | Basic collision detection works correctly with simple object interactions | Collision detection has issues or doesn't work consistently |
| **Code Quality** | Clean, well-organized code with proper class structure, meaningful variable names, and appropriate comments | Code works but could be better organized or documented | Code is difficult to understand or poorly structured |
| **User Interaction** | Responsive controls with smooth gameplay, clear visual feedback, and engaging mechanics | Basic controls work with adequate feedback | Controls are unresponsive or confusing |
| **Creativity** | Original concept with unique features, visual polish, and innovative collision behaviors | Standard implementation with some creative elements | Basic functionality without creative enhancements |

@ -4,11 +4,13 @@
[Pre-lecture quiz](https://ff-quizzes.netlify.app/web/quiz/37)
In this lesson, you'll learn how to add scoring to a game and calculate lives.
Ready to make your space game feel like a real game? Let's add scoring points and managing lives - the core mechanics that transformed early arcade games like Space Invaders from simple demonstrations into addictive entertainment. This is where your game becomes truly playable.
## Draw text on the screen
## Drawing Text on Screen - Your Game's Voice
To be able to display a game score on the screen, you'll need to know how to place text on the screen. The answer is using the `fillText()` method on the canvas object. You can also control other aspects like what font to use, the color of the text and even its alignment (left, right, center). Below is some code drawing some text on the screen.
To display your score, we need to learn how to render text on the canvas. The `fillText()` method is your primary tool for this - it's the same technique used in classic arcade games to show scores and status information.
You have complete control over the text appearance:
```javascript
ctx.font = "30px Arial";
@ -17,22 +19,24 @@ ctx.textAlign = "right";
ctx.fillText("show this on the screen", 0, 0);
```
✅ Read more about [how to add text to a canvas](https://developer.mozilla.org/docs/Web/API/Canvas_API/Tutorial/Drawing_text), and feel free to make yours look fancier!
✅ Dive deeper into [adding text to a canvas](https://developer.mozilla.org/docs/Web/API/Canvas_API/Tutorial/Drawing_text) - you might be surprised at how creative you can get with fonts and styling!
## Lives - More Than Just a Number
## Life, as a game concept
In game design, a "life" represents the player's margin for error. This concept dates back to pinball machines, where you'd get multiple balls to play with. In early video games like Asteroids, lives gave players permission to take risks and learn from mistakes.
The concept of having a life in a game is only a number. In the context of a space game it's common to assign a set of lives that get deducted one by one when your ship takes damage. It's nice if you can show a graphical representation of this like miniships or hearts instead of a number.
Visual representation matters significantly - displaying ship icons instead of just "Lives: 3" creates immediate visual recognition, similar to how early arcade cabinets used iconography to communicate across language barriers.
## What to build
## Building Your Game's Reward System
Let's add the following to your game:
Now we'll implement the core feedback systems that keep players engaged:
- **Game score**: For every enemy ship that is destroyed, the hero should be awarded some points, we suggest a 100 points per ship. The game score should be shown in the bottom left.
- **Life**: Your ship has three lives. You lose a life every time an enemy ship collides with you. A life score should be displayed at the bottom right and be made out of the following graphic ![life image](solution/assets/life.png).
- **Scoring system**: Each destroyed enemy ship awards 100 points (round numbers are easier for players to calculate mentally). The score displays in the bottom left corner.
- **Life counter**: Your hero starts with three lives - a standard established by early arcade games to balance challenge with playability. Each collision with an enemy costs one life. We'll display remaining lives in the bottom right using ship icons ![life image](solution/assets/life.png).
## Recommended steps
## Let's Get Building!
Locate the files that have been created for you in the `your-work` sub folder. It should contain the following:
First, set up your workspace. Navigate to the files in your `your-work` sub folder. You should see these files:
```bash
-| assets
@ -44,24 +48,24 @@ Locate the files that have been created for you in the `your-work` sub folder. I
-| package.json
```
You start your project the `your_work` folder by typing:
To test your game, start the development server from the `your_work` folder:
```bash
cd your-work
npm start
```
The above will start a HTTP Server on address `http://localhost:5000`. Open up a browser and input that address, right now it should render the hero and all the enemies, and as you hit your left and right arrows, the hero moves and can shoot down enemies.
This runs a local server at `http://localhost:5000`. Open this address in your browser to see your game. Test the controls with arrow keys and try shooting enemies to verify everything works.
### Add code
### Time to Code!
1. **Copy over the needed assets** from the `solution/assets/` folder into `your-work` folder; you will add a `life.png` asset. Add the lifeImg to the window.onload function:
1. **Grab the visual assets you'll need**. Copy the `life.png` asset from the `solution/assets/` folder into your `your-work` folder. Then add the lifeImg to your window.onload function:
```javascript
lifeImg = await loadTexture("assets/life.png");
```
1. Add the `lifeImg` to the list of assets:
1. Don't forget to add the `lifeImg` to your assets list:
```javascript
let heroImg,
@ -71,9 +75,9 @@ The above will start a HTTP Server on address `http://localhost:5000`. Open up a
eventEmitter = new EventEmitter();
```
2. **Add variables**. Add code that represents your total score (0) and lives left (3), display these scores on a screen.
2. **Set up your game variables**. Add some code to track your total score (starting at 0) and remaining lives (starting at 3). We'll display these on screen so players always know where they stand.
3. **Extend `updateGameObjects()` function**. Extend the `updateGameObjects()` function to handle enemy collisions:
3. **Implement collision detection**. Extend your `updateGameObjects()` function to detect when enemies collide with your hero:
```javascript
enemies.forEach(enemy => {
@ -84,15 +88,15 @@ The above will start a HTTP Server on address `http://localhost:5000`. Open up a
})
```
4. **Add `life` and `points`**.
1. **Initialize variables**. Under `this.cooldown = 0` in the `Hero` class, set life and points:
4. **Add life and point tracking to your Hero**.
1. **Initialize the counters**. Under `this.cooldown = 0` in your `Hero` class, set up life and points:
```javascript
this.life = 3;
this.points = 0;
```
1. **Draw variables on screen**. Draw these values to screen:
1. **Show these values to the player**. Create functions to draw these values on screen:
```javascript
function drawLife() {
@ -119,18 +123,18 @@ The above will start a HTTP Server on address `http://localhost:5000`. Open up a
```
1. **Add methods to Game loop**. Make sure you add these functions to your window.onload function under `updateGameObjects()`:
1. **Hook everything into your game loop**. Add these functions to your window.onload function right after `updateGameObjects()`:
```javascript
drawPoints();
drawLife();
```
1. **Implement game rules**. Implement the following game rules:
1. **Implement game consequences and rewards**. Now we'll add the feedback systems that make player actions meaningful:
1. **For every hero and enemy collision**, deduct a life.
1. **Collisions cost lives**. Every time your hero crashes into an enemy, you should lose a life.
Extend the `Hero` class to do this deduction:
Add this method to your `Hero` class:
```javascript
decrementLife() {
@ -141,9 +145,9 @@ The above will start a HTTP Server on address `http://localhost:5000`. Open up a
}
```
2. **For every laser that hits an enemy**, increase game score with a 100 points.
2. **Shooting enemies earns points**. Each successful hit awards 100 points, providing immediate positive feedback for accurate shooting.
Extend the Hero class to do this increment:
Extend your Hero class with this increment method:
```javascript
incrementPoints() {
@ -151,7 +155,7 @@ The above will start a HTTP Server on address `http://localhost:5000`. Open up a
}
```
Add these functions to your Collision Event Emitters:
Now connect these functions to your collision events:
```javascript
eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => {
@ -166,9 +170,11 @@ The above will start a HTTP Server on address `http://localhost:5000`. Open up a
});
```
✅ Do a little research to discover other games that are created using JavaScript/Canvas. What are their common traits?
✅ Curious about other games built with JavaScript and Canvas? Do some exploring - you might be amazed at what's possible!
After implementing these features, test your game to see the complete feedback system in action. You should see life icons in the bottom right, your score in the bottom left, and watch as collisions reduce lives while successful shots increase your score.
By the end of this work, you should see the small 'life' ships at the bottom right, points at the bottom left, and you should see your life count decrement as you collide with enemies and your points increment when you shoot enemies. Well done! Your game is almost complete.
Your game now has the essential mechanics that made early arcade games so compelling - clear goals, immediate feedback, and meaningful consequences for player actions.
---
@ -184,7 +190,7 @@ Use the Agent mode to complete the following challenge:
## 🚀 Challenge
Your code is almost complete. Can you envision your next steps?
You now have a functional game with scoring and lives. Consider what additional features might enhance the player experience.
## Post-Lecture Quiz
@ -192,7 +198,7 @@ Your code is almost complete. Can you envision your next steps?
## Review & Self Study
Research some ways that you can increment and decrement game scores and lives. There are some interesting game engines like [PlayFab](https://playfab.com). How could using one of these would enhance your game?
Want to explore more? Research different approaches to game scoring and life systems. There are fascinating game engines out there like [PlayFab](https://playfab.com) that handle scoring, leaderboards, and player progression. How might integrating something like that take your game to the next level?
## Assignment

@ -1,32 +1,50 @@
# Build a Space Game Part 6: End and Restart
Every great game needs clear end conditions and a smooth restart mechanism. You've built an impressive space game with movement, combat, and scoring - now it's time to add the final pieces that make it feel complete.
Your game currently runs indefinitely, like the Voyager probes that NASA launched in 1977 - still traveling through space decades later. While that's fine for space exploration, games need defined endpoints to create satisfying experiences.
Today, we'll implement proper win/lose conditions and a restart system. By the end of this lesson, you'll have a polished game that players can complete and replay, just like the classic arcade games that defined the medium.
## Pre-Lecture Quiz
[Pre-lecture quiz](https://ff-quizzes.netlify.app/web/quiz/39)
There are different ways to express and *end condition* in a game. It's up to you as the creator of the game to say why the game has ended. Here are some reasons, if we assume we are talking about the space game you have been building so far:
## Understanding Game End Conditions
When should your game end? This fundamental question has shaped game design since the early arcade era. Pac-Man ends when you're caught by ghosts or clear all dots, while Space Invaders ends when aliens reach the bottom or you destroy them all.
As the game creator, you define the victory and defeat conditions. For our space game, here are proven approaches that create engaging gameplay:
- **`N` Enemy ships have been destroyed**: It's quite common if you divide up a game into different levels that you need to destroy `N` Enemy ships to complete a level
- **Your ship has been destroyed**: There are definitely games where you lose the game if your ship is destroyed. Another common approach is that you have the concept of lives. Every time a your ship is destroyed it deducts a life. Once all lives have been lost then you lose the game.
- **You've collected `N` points**: Another common end condition is for you to collect points. How you get points is up to you but it's quite common to assign points to various activities like destroying an enemy ship or maybe collect items that items *drop* when they are destroyed.
- **Complete a level**: This might involve several conditions such as `X` enemy ships destroyed, `Y` points collected or maybe that a specific item has been collected.
## Restarting
## Implementing Game Restart Functionality
Good games encourage replayability through smooth restart mechanisms. When players complete a game (or meet defeat), they often want to try again immediately - whether to beat their score or improve their performance.
Tetris exemplifies this perfectly: when your blocks reach the top, you can instantly start a new game without navigating complex menus. We'll build a similar restart system that cleanly resets the game state and gets players back into action quickly.
**Reflection**: Think about the games you've played. Under what conditions do they end, and how are you prompted to restart? What makes a restart experience feel smooth versus frustrating?
If people enjoy your game they are likely to want to replay it. Once the game ends for whatever reason you should offer an alternative to restart.
## What You'll Build
✅ Think a bit about under what conditions you find a game ends, and then how you are prompted to restart
You'll implement the final features that transform your project into a complete game experience. These elements distinguish polished games from basic prototypes.
## What to build
**Here's what we're adding today:**
You will be adding these rules to your game:
1. **Victory condition**: Blast all the enemies and get a proper celebration (you've earned it!)
2. **Defeat condition**: Run out of lives and face the music with a defeat screen
3. **Restart mechanism**: Hit Enter to jump right back in - because one game is never enough
4. **State management**: Clean slate every time - no leftover enemies or weird glitches from the last game
1. **Winning the game**. Once all enemy ships have been destroyed, you win the game. Additionally display some kind of victory message.
1. **Restart**. Once all your lives are lost or the game is won, you should offer a way to restart the game. Remember! You will need to reinitialize the game and the previous game state should be cleared.
## Getting Started
## Recommended steps
Let's prepare your development environment. You should have all your space game files from the previous lessons ready.
Locate the files that have been created for you in the `your-work` sub folder. It should contain the following:
**Your project should look something like this:**
```bash
-| assets
@ -39,171 +57,247 @@ Locate the files that have been created for you in the `your-work` sub folder. I
-| package.json
```
You start your project the `your_work` folder by typing:
**Start your development server:**
```bash
cd your-work
npm start
```
The above will start a HTTP Server on address `http://localhost:5000`. Open up a browser and input that address. Your game should be in a playable state.
**This command:**
- Runs a local server on `http://localhost:5000`
- Serves your files properly
- Automatically refreshes when you make changes
Open `http://localhost:5000` in your browser and verify your game is running. You should be able to move, shoot, and interact with enemies. Once confirmed, we can proceed with the implementation.
> 💡 **Pro Tip**: To avoid warnings in Visual Studio Code, declare `gameLoopId` at the top of your file as `let gameLoopId;` instead of declaring it inside the `window.onload` function. This follows modern JavaScript variable declaration best practices.
## Implementation Steps
### Step 1: Create End Condition Tracking Functions
We need functions to monitor when the game should end. Like sensors on the International Space Station that constantly monitor critical systems, these functions will continuously check the game state.
```javascript
function isHeroDead() {
return hero.life <= 0;
}
function isEnemiesDead() {
const enemies = gameObjects.filter((go) => go.type === "Enemy" && !go.dead);
return enemies.length === 0;
}
```
> tip: to avoid warnings in Visual Studio Code, edit the `window.onload` function to call `gameLoopId` as is (without `let`), and declare the gameLoopId at the top of the file, independently: `let gameLoopId;`
**Here's what's happening under the hood:**
- **Checks** if our hero is out of lives (ouch!)
- **Counts** how many enemies are still alive and kicking
- **Returns** `true` when the battlefield is clear of enemies
- **Uses** simple true/false logic to keep things straightforward
- **Filters** through all game objects to find the survivors
### Add code
### Step 2: Update Event Handlers for End Conditions
1. **Track end condition**. Add code that keeps track of the number of enemies, or if the hero ship has been destroyed by adding these two functions:
Now we'll connect these condition checks to the game's event system. Every time a collision occurs, the game will evaluate whether it triggers an end condition. This creates immediate feedback for critical game events.
```javascript
function isHeroDead() {
return hero.life <= 0;
```javascript
eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => {
first.dead = true;
second.dead = true;
hero.incrementPoints();
if (isEnemiesDead()) {
eventEmitter.emit(Messages.GAME_END_WIN);
}
});
eventEmitter.on(Messages.COLLISION_ENEMY_HERO, (_, { enemy }) => {
enemy.dead = true;
hero.decrementLife();
if (isHeroDead()) {
eventEmitter.emit(Messages.GAME_END_LOSS);
return; // loss before victory
}
if (isEnemiesDead()) {
eventEmitter.emit(Messages.GAME_END_WIN);
}
});
eventEmitter.on(Messages.GAME_END_WIN, () => {
endGame(true);
});
eventEmitter.on(Messages.GAME_END_LOSS, () => {
endGame(false);
});
```
**What's going on here:**
- **Laser hits enemy**: Both disappear, you get points, and we check if you've won
- **Enemy hits you**: You lose a life, and we check if you're still breathing
- **Smart ordering**: We check for defeat first (nobody wants to win and lose at the same time!)
- **Instant reactions**: As soon as something important happens, the game knows about it
### Step 3: Add New Message Constants
You'll need to add new message types to your `Messages` constant object. These constants help maintain consistency and prevent typos in your event system.
```javascript
GAME_END_LOSS: "GAME_END_LOSS",
GAME_END_WIN: "GAME_END_WIN",
```
**In the above, we've:**
- **Added** constants for game end events to maintain consistency
- **Used** descriptive names that clearly indicate the event purpose
- **Followed** the existing naming convention for message types
### Step 4: Implement Restart Controls
Now you'll add keyboard controls that allow players to restart the game. The Enter key is a natural choice since it's commonly associated with confirming actions and starting new games.
**Add Enter key detection to your existing keydown event listener:**
```javascript
else if(evt.key === "Enter") {
eventEmitter.emit(Messages.KEY_EVENT_ENTER);
}
```
function isEnemiesDead() {
const enemies = gameObjects.filter((go) => go.type === "Enemy" && !go.dead);
return enemies.length === 0;
**Add the new message constant:**
```javascript
KEY_EVENT_ENTER: "KEY_EVENT_ENTER",
```
**What you need to know:**
- **Extends** your existing keyboard event handling system
- **Uses** the Enter key as the restart trigger for intuitive user experience
- **Emits** a custom event that other parts of your game can listen for
- **Maintains** the same pattern as your other keyboard controls
### Step 5: Create the Message Display System
Your game needs to communicate results clearly to players. We'll create a message system that displays victory and defeat states using color-coded text, similar to the terminal interfaces of early computer systems where green indicated success and red signaled errors.
**Create the `displayMessage()` function:**
```javascript
function displayMessage(message, color = "red") {
ctx.font = "30px Arial";
ctx.fillStyle = color;
ctx.textAlign = "center";
ctx.fillText(message, canvas.width / 2, canvas.height / 2);
}
```
**Step by step, here's what's happening:**
- **Sets** the font size and family for clear, readable text
- **Applies** a color parameter with "red" as the default for warnings
- **Centers** the text horizontally and vertically on the canvas
- **Uses** modern JavaScript default parameters for flexible color options
- **Leverages** the canvas 2D context for direct text rendering
**Create the `endGame()` function:**
```javascript
function endGame(win) {
clearInterval(gameLoopId);
// Set a delay to ensure any pending renders complete
setTimeout(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
if (win) {
displayMessage(
"Victory!!! Pew Pew... - Press [Enter] to start a new game Captain Pew Pew",
"green"
);
} else {
displayMessage(
"You died !!! Press [Enter] to start a new game Captain Pew Pew"
);
}
```
1. **Add logic to message handlers**. Edit the `eventEmitter` to handle these conditions:
```javascript
eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => {
first.dead = true;
second.dead = true;
hero.incrementPoints();
if (isEnemiesDead()) {
eventEmitter.emit(Messages.GAME_END_WIN);
}
});
eventEmitter.on(Messages.COLLISION_ENEMY_HERO, (_, { enemy }) => {
enemy.dead = true;
hero.decrementLife();
if (isHeroDead()) {
eventEmitter.emit(Messages.GAME_END_LOSS);
return; // loss before victory
}
if (isEnemiesDead()) {
eventEmitter.emit(Messages.GAME_END_WIN);
}
});
eventEmitter.on(Messages.GAME_END_WIN, () => {
endGame(true);
});
eventEmitter.on(Messages.GAME_END_LOSS, () => {
endGame(false);
});
```
1. **Add new message types**. Add these Messages to the constants object:
```javascript
GAME_END_LOSS: "GAME_END_LOSS",
GAME_END_WIN: "GAME_END_WIN",
```
2. **Add restart code** code that restarts the game at the press of a selected button.
1. **Listen to key press `Enter`**. Edit your window's eventListener to listen for this press:
```javascript
else if(evt.key === "Enter") {
eventEmitter.emit(Messages.KEY_EVENT_ENTER);
}
```
1. **Add restart message**. Add this Message to your Messages constant:
```javascript
KEY_EVENT_ENTER: "KEY_EVENT_ENTER",
```
1. **Implement game rules**. Implement the following game rules:
1. **Player win condition**. When all enemy ships are destroyed, display a victory message.
1. First, create a `displayMessage()` function:
```javascript
function displayMessage(message, color = "red") {
ctx.font = "30px Arial";
ctx.fillStyle = color;
ctx.textAlign = "center";
ctx.fillText(message, canvas.width / 2, canvas.height / 2);
}
```
1. Create an `endGame()` function:
```javascript
function endGame(win) {
clearInterval(gameLoopId);
// set a delay so we are sure any paints have finished
setTimeout(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
if (win) {
displayMessage(
"Victory!!! Pew Pew... - Press [Enter] to start a new game Captain Pew Pew",
"green"
);
} else {
displayMessage(
"You died !!! Press [Enter] to start a new game Captain Pew Pew"
);
}
}, 200)
}
```
1. **Restart logic**. When all lives are lost or the player won the game, display that the game can be restarted. Additionally restart the game when the *restart* key is hit (you can decide what key should be mapped to restart).
1. Create the `resetGame()` function:
```javascript
function resetGame() {
if (gameLoopId) {
clearInterval(gameLoopId);
eventEmitter.clear();
initGame();
gameLoopId = setInterval(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawPoints();
drawLife();
updateGameObjects();
drawGameObjects(ctx);
}, 100);
}
}
```
1. Add a call to the `eventEmitter` to reset the game in `initGame()`:
```javascript
eventEmitter.on(Messages.KEY_EVENT_ENTER, () => {
resetGame();
});
```
1. Add a `clear()` function to the EventEmitter:
```javascript
clear() {
this.listeners = {};
}
```
👽 💥 🚀 Congratulations, Captain! Your game is complete! Well done! 🚀 💥 👽
---
}, 200)
}
```
**What this function does:**
- **Freezes** everything in place - no more moving ships or lasers
- **Takes** a tiny pause (200ms) to let the last frame finish drawing
- **Wipes** the screen clean and paints it black for dramatic effect
- **Shows** different messages for winners and losers
- **Color codes** the news - green for good, red for... well, not so good
- **Tells** players exactly how to jump back in
### Step 6: Implement Game Reset Functionality
The reset system needs to completely clean up the current game state and initialize a fresh game session. This ensures players get a clean start without any leftover data from the previous game.
**Create the `resetGame()` function:**
```javascript
function resetGame() {
if (gameLoopId) {
clearInterval(gameLoopId);
eventEmitter.clear();
initGame();
gameLoopId = setInterval(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawPoints();
drawLife();
updateGameObjects();
drawGameObjects(ctx);
}, 100);
}
}
```
**Let's understand each part:**
- **Checks** if a game loop is currently running before resetting
- **Clears** the existing game loop to stop all current game activity
- **Removes** all event listeners to prevent memory leaks
- **Reinitializes** the game state with fresh objects and variables
- **Starts** a new game loop with all the essential game functions
- **Maintains** the same 100ms interval for consistent game performance
**Add the Enter key event handler to your `initGame()` function:**
```javascript
eventEmitter.on(Messages.KEY_EVENT_ENTER, () => {
resetGame();
});
```
**Add the `clear()` method to your EventEmitter class:**
```javascript
clear() {
this.listeners = {};
}
```
**Key points to remember:**
- **Connects** the Enter key press to the reset game functionality
- **Registers** this event listener during game initialization
- **Provides** a clean way to remove all event listeners when resetting
- **Prevents** memory leaks by clearing event handlers between games
- **Resets** the listeners object to an empty state for fresh initialization
## Congratulations! 🎉
👽 💥 🚀 You've successfully built a complete game from the ground up. Like the programmers who created the first video games in the 1970s, you've transformed lines of code into an interactive experience with proper game mechanics and user feedback. 🚀 💥 👽
**You've accomplished:**
- **Implemented** complete win and lose conditions with user feedback
- **Created** a seamless restart system for continuous gameplay
- **Designed** clear visual communication for game states
- **Managed** complex game state transitions and cleanup
- **Assembled** all components into a cohesive, playable game
## GitHub Copilot Agent Challenge 🚀
@ -213,9 +307,39 @@ Use the Agent mode to complete the following challenge:
**Prompt:** Create a multi-level space game system where each level has more enemy ships with increased speed and health. Add a scoring multiplier that increases with each level, and implement power-ups (like rapid fire or shield) that randomly appear when enemies are destroyed. Include a level completion bonus and display the current level on screen alongside the existing score and lives.
## 🚀 Challenge
Learn more about [agent mode](https://code.visualstudio.com/blogs/2025/02/24/introducing-copilot-agent-mode) here.
## 🚀 Optional Enhancement Challenge
**Add Audio to Your Game**: Enhance your gameplay experience by implementing sound effects! Consider adding audio for:
- **Laser shots** when the player fires
- **Enemy destruction** when ships are hit
- **Hero damage** when the player takes hits
- **Victory music** when the game is won
- **Defeat sound** when the game is lost
**Audio implementation example:**
```javascript
// Create audio objects
const laserSound = new Audio('assets/laser.wav');
const explosionSound = new Audio('assets/explosion.wav');
// Play sounds during game events
function playLaserSound() {
laserSound.currentTime = 0; // Reset to beginning
laserSound.play();
}
```
**What you need to know:**
- **Creates** Audio objects for different sound effects
- **Resets** the `currentTime` to allow rapid-fire sound effects
- **Handles** browser autoplay policies by triggering sounds from user interactions
- **Manages** audio volume and timing for better game experience
Add a sound! Can you add a sound to enhance your game play, maybe when there's a laser hit, or the hero dies or wins? Have a look at this [sandbox](https://www.w3schools.com/jsref/tryit.asp?filename=tryjsref_audio_play) to learn how to play sound using JavaScript
> 💡 **Learning Resource**: Explore this [audio sandbox](https://www.w3schools.com/jsref/tryit.asp?filename=tryjsref_audio_play) to learn more about implementing audio in JavaScript games.
## Post-Lecture Quiz

@ -1,19 +1,157 @@
# Build a Sample Game
## Instructions
## Assignment Overview
Try building a small game where you practice on different end conditions. Vary between getting a number of points, the hero loses all lives or all monsters are defeated. Build something simple like a console based adventure game. Use the below game flow as inspiration:
Now that you've mastered game end conditions and restart functionality in your space game, it's time to apply these concepts to a completely new gaming experience. You'll design and build your own game that demonstrates different end condition patterns and restart mechanics.
This assignment challenges you to think creatively about game design while practicing the technical skills you've learned. You'll explore different victory and defeat scenarios, implement player progression, and create engaging restart experiences.
## Project Requirements
### Core Game Features
Your game must include the following essential elements:
**End Condition Variety**: Implement at least two different ways the game can end:
- **Point-based victory**: Player reaches a target score or collects specific items
- **Life-based defeat**: Player loses all available lives or health points
- **Objective completion**: All enemies defeated, specific items collected, or goals achieved
- **Time-based**: Game ends after a set duration or countdown reaches zero
**Restart Functionality**:
- **Clear game state**: Remove all previous game objects and reset variables
- **Reinitialize systems**: Start fresh with new player stats, enemies, and objectives
- **User-friendly controls**: Provide clear instructions for restarting the game
**Player Feedback**:
- **Victory messages**: Celebrate player achievements with positive feedback
- **Defeat messages**: Provide encouraging messages that motivate replay
- **Progress indicators**: Show current score, lives, or objective status
### Game Ideas and Inspiration
Choose one of these game concepts or create your own:
#### 1. Console Adventure Game
Create a text-based adventure with combat mechanics:
```
Hero> Strikes with broadsword - orc takes 3p damage
Orc> Hits with club - hero takes 2p damage
Orc> Hits with club - hero takes 2p damage
Hero> Kicks - orc takes 1p damage
Game> Orc is defeated - Hero collects 2 coins
Game> ****No more monsters, you have conquered the evil fortress****
```
## Rubric
**Key features to implement:**
- **Turn-based combat** with different attack options
- **Health points** for both player and enemies
- **Inventory system** for collecting coins or items
- **Multiple enemy types** with varying difficulty
- **Victory condition** when all enemies are defeated
#### 2. Collection Game
- **Objective**: Collect specific items while avoiding obstacles
- **End conditions**: Reach target collection count or lose all lives
- **Progression**: Items become harder to reach as game continues
#### 3. Puzzle Game
- **Objective**: Solve increasingly difficult puzzles
- **End conditions**: Complete all levels or run out of moves/time
- **Restart**: Reset to first level with cleared progress
#### 4. Defense Game
- **Objective**: Protect your base from waves of enemies
- **End conditions**: Survive all waves (victory) or base is destroyed (defeat)
- **Progression**: Enemy waves increase in difficulty and number
## Implementation Guidelines
### Getting Started
1. **Plan your game design**:
- Sketch the basic gameplay loop
- Define your end conditions clearly
- Identify what data needs to be reset on restart
2. **Set up your project structure**:
```
my-game/
├── index.html
├── style.css
├── game.js
└── README.md
```
3. **Create your core game loop**:
- Initialize game state
- Handle user input
- Update game logic
- Check end conditions
- Render current state
### Technical Requirements
**Use Modern JavaScript**:
- Apply `const` and `let` for variable declarations
- Use arrow functions where appropriate
- Implement ES6+ features like template literals and destructuring
**Event-Driven Architecture**:
- Create event handlers for user interactions
- Implement game state changes through events
- Use event listeners for restart functionality
**Clean Code Practices**:
- Write functions with single responsibilities
- Use descriptive variable and function names
- Add comments explaining game logic and rules
- Organize code into logical sections
## Submission Requirements
### Deliverables
1. **Complete game files**: All HTML, CSS, and JavaScript files needed to run your game
2. **README.md**: Documentation explaining:
- How to play your game
- What end conditions you implemented
- Instructions for restarting
- Any special features or mechanics
3. **Code comments**: Clear explanations of your game logic and algorithms
### Testing Checklist
Before submitting, verify that your game:
- [ ] **Runs without errors** in the browser console
- [ ] **Implements multiple end conditions** as specified
- [ ] **Restarts properly** with clean state reset
- [ ] **Provides clear feedback** to players about game status
- [ ] **Uses modern JavaScript** syntax and best practices
- [ ] **Includes comprehensive documentation** in README.md
## Assessment Rubric
| Criteria | Exemplary (4) | Proficient (3) | Developing (2) | Beginning (1) |
|----------|---------------|----------------|----------------|--------------|
| **Game Functionality** | Complete game with multiple end conditions, smooth restart, and polished gameplay experience | Full game with basic end conditions and functional restart mechanism | Partial game with some end conditions implemented, restart may have minor issues | Incomplete game with limited functionality and significant bugs |
| **Code Quality** | Clean, well-organized code using modern JavaScript practices, comprehensive comments, and excellent structure | Good code organization with modern syntax, adequate comments, and clear structure | Basic code organization with some modern practices, minimal comments | Poor code organization, outdated syntax, lacking comments and structure |
| **User Experience** | Intuitive gameplay with clear instructions, excellent feedback, and engaging end/restart experience | Good gameplay with adequate instructions and feedback, functional end/restart | Basic gameplay with minimal instructions, limited feedback on game state | Confusing gameplay with unclear instructions and poor user feedback |
| **Technical Implementation** | Demonstrates mastery of game development concepts, event handling, and state management | Shows solid understanding of game concepts with good implementation | Basic understanding with acceptable implementation | Limited understanding with poor implementation |
| **Documentation** | Comprehensive README with clear instructions, well-documented code, and thorough testing evidence | Good documentation with clear instructions and adequate code comments | Basic documentation with minimal instructions | Poor or missing documentation |
### Grading Scale
- **Exemplary (16-20 points)**: Exceeds expectations with creative features and polished implementation
- **Proficient (12-15 points)**: Meets all requirements with solid execution
- **Developing (8-11 points)**: Meets most requirements with minor issues
- **Beginning (4-7 points)**: Meets some requirements but needs significant improvement
## Additional Learning Resources
- [MDN Game Development Guide](https://developer.mozilla.org/en-US/docs/Games)
- [JavaScript Game Development Tutorials](https://developer.mozilla.org/en-US/docs/Games/Tutorials)
- [Canvas API Documentation](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API)
- [Game Design Principles](https://www.gamasutra.com/blogs/)
| Criteria | Exemplary | Adequate | Needs Improvement |
| -------- | ---------------------- | --------------------------- | -------------------------- |
| | full game is presented | game is partially presented | partial game contains bugs |
> 💡 **Pro Tip**: Start simple and add features incrementally. A well-polished simple game is better than a complex game with bugs!

@ -1,18 +1,38 @@
# Build a Banking App Part 1: HTML Templates and Routes in a Web App
## Pre-Lecture Quiz
When Apollo 11's guidance computer navigated to the moon in 1969, it had to switch between different programs without restarting the entire system. Modern web applications work similarly they change what you see without reloading everything from scratch. This creates the smooth, responsive experience users expect today.
[Pre-lecture quiz](https://ff-quizzes.netlify.app/web/quiz/41)
Unlike traditional websites that reload entire pages for every interaction, modern web apps update only the parts that need changing. This approach, much like how mission control switches between different displays while maintaining constant communication, creates that fluid experience we've come to expect.
Here's what makes the difference so dramatic:
### Introduction
| Traditional Multi-Page Apps | Modern Single-Page Apps |
|----------------------------|-------------------------|
| **Navigation** | Full page reload for each screen | Instant content switching |
| **Performance** | Slower due to complete HTML downloads | Faster with partial updates |
| **User Experience** | Jarring page flashes | Smooth, app-like transitions |
| **Data Sharing** | Difficult between pages | Easy state management |
| **Development** | Multiple HTML files to maintain | Single HTML with dynamic templates |
Since the advent of JavaScript in browsers, websites are becoming more interactive and complex than ever. Web technologies are now commonly used to create fully functional applications that runs directly into a browser that we call [web applications](https://en.wikipedia.org/wiki/Web_application). As Web apps are highly interactive, users do not want to wait for a full page reload every time an action is performed. That's why JavaScript is used to update the HTML directly using the DOM, to provide a smoother user experience.
**Understanding the evolution:**
- **Traditional apps** require server requests for every navigation action
- **Modern SPAs** load once and update content dynamically using JavaScript
- **User expectations** now favor instant, seamless interactions
- **Performance benefits** include reduced bandwidth and faster responses
In this lesson, we're going to lay out the foundations to create bank web app, using HTML templates to create multiple screens that can be displayed and updated without having to reload the entire HTML page.
In this lesson, we'll build a banking app with multiple screens that flow together seamlessly. Like how scientists use modular instruments that can be reconfigured for different experiments, we'll use HTML templates as reusable components that can be displayed as needed.
### Prerequisite
You'll work with HTML templates (reusable blueprints for different screens), JavaScript routing (the system that switches between screens), and the browser's history API (which keeps the back button working as expected). These are the same fundamental techniques used by frameworks like React, Vue, and Angular.
You need a local web server to test the web app we'll build in this lesson. If you don't have one, you can install [Node.js](https://nodejs.org) and use the command `npx lite-server` from your project folder. It will create a local web server and open your app in a browser.
By the end, you'll have a working banking app that demonstrates professional single-page application principles.
## Pre-Lecture Quiz
[Pre-lecture quiz](https://ff-quizzes.netlify.app/web/quiz/41)
### What You'll Need
We'll need a local web server to test our banking app don't worry, it's easier than it sounds! If you don't already have one set up, just install [Node.js](https://nodejs.org) and run `npx lite-server` from your project folder. This handy command spins up a local server and automatically opens your app in the browser.
### Preparation
@ -32,9 +52,35 @@ On your computer, create a folder named `bank` with a file named `index.html` in
</html>
```
**Here's what this boilerplate provides:**
- **Establishes** the HTML5 document structure with proper DOCTYPE declaration
- **Configures** character encoding as UTF-8 for international text support
- **Enables** responsive design with the viewport meta tag for mobile compatibility
- **Sets** a descriptive title that appears in the browser tab
- **Creates** a clean body section where we'll build our application
> 📁 **Project Structure Preview**
>
> **By the end of this lesson, your project will contain:**
> ```
> bank/
> ├── index.html <!-- Main HTML with templates -->
> ├── app.js <!-- Routing and navigation logic -->
> └── style.css <!-- (Optional for future lessons) -->
> ```
>
> **File responsibilities:**
> - **index.html**: Contains all templates and provides the app structure
> - **app.js**: Handles routing, navigation, and template management
> - **Templates**: Define the UI for login, dashboard, and other screens
---
## HTML templates
## HTML Templates
Templates solve a fundamental problem in web development. When Gutenberg invented movable type printing in the 1440s, he realized that instead of carving entire pages, he could create reusable letter blocks and arrange them as needed. HTML templates work on the same principle instead of creating separate HTML files for each screen, you define reusable structures that can be displayed when needed.
Think of templates as blueprints for different parts of your app. Just as an architect creates one blueprint and uses it multiple times rather than redrawing identical rooms, we create templates once and instantiate them as needed. The browser keeps these templates hidden until JavaScript activates them.
If you want to create multiple screens for a web page, one solution would be to create one HTML file for every screen you want to display. However, this solution comes with some inconvenience:
@ -43,17 +89,21 @@ If you want to create multiple screens for a web page, one solution would be to
Another approach is to have only one HTML file, and define multiple [HTML templates](https://developer.mozilla.org/docs/Web/HTML/Element/template) using the `<template>` element. A template is a reusable HTML block that is not displayed by the browser, and needs to be instantiated at runtime using JavaScript.
### Task
### Let's Build It
We'll create a bank app with two screens: the login page and the dashboard. First, let's add in the HTML body a placeholder element that we'll use to instantiate the different screens of our app:
We're going to create a bank app with two main screens: a login page and a dashboard. First, let's add a placeholder element to our HTML body this is where all our different screens will appear:
```html
<div id="app">Loading...</div>
```
We're giving it an `id` to make it easier to locate it with JavaScript later.
**Understanding this placeholder:**
- **Creates** a container with the ID "app" where all screens will be displayed
- **Shows** a loading message until the JavaScript initializes the first screen
- **Provides** a single mounting point for our dynamic content
- **Enables** easy targeting from JavaScript using `document.getElementById()`
> Tip: since the content of this element will be replaced, we can put in a loading message or indicator that will be shown while the app is loading.
> 💡 **Pro Tip**: Since the content of this element will be replaced, we can put in a loading message or indicator that will be shown while the app is loading.
Next, let's add below the HTML template for the login page. For now we'll only put in there a title and a section containing a link that we'll use to perform the navigation.
@ -66,6 +116,12 @@ Next, let's add below the HTML template for the login page. For now we'll only p
</template>
```
**Breaking down this login template:**
- **Defines** a template with the unique identifier "login" for JavaScript targeting
- **Includes** a main heading that establishes the app's branding
- **Contains** a semantic `<section>` element to group related content
- **Provides** a navigation link that will route users to the dashboard
Then we'll add another HTML template for the dashboard page. This page will contain different sections:
- A header with a title and a logout link
@ -97,11 +153,24 @@ Then we'll add another HTML template for the dashboard page. This page will cont
</template>
```
> Tip: when creating HTML templates, if you want to see what it will look like, you can comment out the `<template>` and `</template>` lines by enclosing them with `<!-- -->`.
**Let's understand each part of this dashboard:**
- **Structures** the page with a semantic `<header>` element containing navigation
- **Displays** the app title consistently across screens for branding
- **Provides** a logout link that routes back to the login screen
- **Shows** the current account balance in a dedicated section
- **Organizes** transaction data using a properly structured HTML table
- **Defines** table headers for Date, Object, and Amount columns
- **Leaves** the table body empty for dynamic content injection later
> 💡 **Pro Tip**: When creating HTML templates, if you want to see what it will look like, you can comment out the `<template>` and `</template>` lines by enclosing them with `<!-- -->`.
✅ Why do you think we use `id` attributes on the templates? Could we use something else like classes?
## Displaying templates with JavaScript
## Bringing Templates to Life with JavaScript
Now we need to make our templates functional. Like how a 3D printer takes a digital blueprint and creates a physical object, JavaScript takes our hidden templates and creates visible, interactive elements that users can see and use.
The process follows three consistent steps that form the foundation of modern web development. Once you understand this pattern, you'll recognize it across many frameworks and libraries.
If you try your current HTML file in a browser, you'll see that it gets stuck displaying `Loading...`. That's because we need to add some JavaScript code to instantiate and display the HTML templates.
@ -111,6 +180,29 @@ Instantiating a template is usually done in 3 steps:
2. Clone the template element, using [`cloneNode`](https://developer.mozilla.org/docs/Web/API/Node/cloneNode).
3. Attach it to the DOM under a visible element, for example using [`appendChild`](https://developer.mozilla.org/docs/Web/API/Node/appendChild).
```mermaid
flowchart TD
A[🔍 Step 1: Find Template] --> B[📋 Step 2: Clone Template]
B --> C[🔗 Step 3: Attach to DOM]
A1["document.getElementById('login')"] --> A
B1["template.content.cloneNode(true)"] --> B
C1["app.appendChild(view)"] --> C
C --> D[👁️ Template Visible to User]
style A fill:#e1f5fe
style B fill:#f3e5f5
style C fill:#e8f5e8
style D fill:#fff3e0
```
**Visual breakdown of the process:**
- **Step 1** locates the hidden template in the DOM structure
- **Step 2** creates a working copy that can be safely modified
- **Step 3** inserts the copy into the visible page area
- **Result** is a functional screen that users can interact with
✅ Why do we need to clone the template before attaching it to the DOM? What do you think would happen if we skipped this step?
### Task
@ -121,6 +213,12 @@ Create a new file named `app.js` in your project folder and import that file in
<script src="app.js" defer></script>
```
**Understanding this script import:**
- **Links** the JavaScript file to our HTML document
- **Uses** the `defer` attribute to ensure the script runs after HTML parsing completes
- **Enables** access to all DOM elements since they're fully loaded before script execution
- **Follows** modern best practices for script loading and performance
Now in `app.js`, we'll create a new function `updateRoute`:
```js
@ -133,7 +231,12 @@ function updateRoute(templateId) {
}
```
What we do here is exactly the 3 steps described above. We instantiate the template with the id `templateId`, and put its cloned content within our app placeholder. Note that we need to use `cloneNode(true)` to copy the entire subtree of the template.
**Step by step, here's what's happening:**
- **Locates** the template element using its unique ID
- **Creates** a deep copy of the template's content using `cloneNode(true)`
- **Finds** the app container where the content will be displayed
- **Clears** any existing content from the app container
- **Inserts** the cloned template content into the visible DOM
Now call this function with one of the template and look at the result.
@ -141,9 +244,41 @@ Now call this function with one of the template and look at the result.
updateRoute('login');
```
**What this function call accomplishes:**
- **Activates** the login template by passing its ID as a parameter
- **Demonstrates** how to programmatically switch between different app screens
- **Shows** the login screen in place of the "Loading..." message
✅ What's the purpose of this code `app.innerHTML = '';`? What happens without it?
## Creating routes
## Creating Routes
Routing is essentially about connecting URLs to the right content. Consider how early telephone operators used switchboards to connect calls they would take an incoming request and route it to the correct destination. Web routing works similarly, taking a URL request and determining which content to display.
Traditionally, web servers handled this by serving different HTML files for different URLs. Since we're building a single-page app, we need to handle this routing ourselves with JavaScript. This approach gives us more control over the user experience and performance.
```mermaid
flowchart LR
A["🌐 URL Path<br/>/dashboard"] --> B["🗺️ Routes Object<br/>Lookup"]
B --> C["🎯 Template ID<br/>'dashboard'"]
C --> D["📄 Find Template<br/>getElementById"]
D --> E["👁️ Display Screen<br/>Clone & Append"]
F["📍 /login"] --> G["🎯 'login'"]
H["📍 /unknown"] --> I["❌ Not Found"]
I --> J["🔄 Redirect to /login"]
style B fill:#e3f2fd
style E fill:#e8f5e8
style I fill:#ffebee
style J fill:#fff3e0
```
**Understanding the routing flow:**
- **URL changes** trigger a lookup in our routes configuration
- **Valid routes** map to specific template IDs for rendering
- **Invalid routes** trigger fallback behavior to prevent broken states
- **Template rendering** follows the three-step process we learned earlier
When talking about a web app, we call *Routing* the intent to map **URLs** to specific screens that should be displayed. On a website with multiple HTML files, this is done automatically as the file paths are reflected on the URL. For example, with these files in your project folder:
@ -174,6 +309,12 @@ const routes = {
};
```
**Understanding this routes configuration:**
- **Defines** a mapping between URL paths and template identifiers
- **Uses** object syntax where keys are URL paths and values contain template information
- **Enables** easy lookup of which template to display for any given URL
- **Provides** a scalable structure for adding new routes in the future
Now let's modify a bit the `updateRoute` function. Instead of passing directly the `templateId` as an argument, we want to retrieve it by first looking at the current URL, and then use our map to get the corresponding template ID value. We can use [`window.location.pathname`](https://developer.mozilla.org/docs/Web/API/Location/pathname) to get only the path section from the URL.
```js
@ -189,11 +330,36 @@ function updateRoute() {
}
```
**Breaking down what happens here:**
- **Extracts** the current path from the browser's URL using `window.location.pathname`
- **Looks up** the corresponding route configuration in our routes object
- **Retrieves** the template ID from the route configuration
- **Follows** the same template rendering process as before
- **Creates** a dynamic system that responds to URL changes
Here we mapped the routes we declared to the corresponding template. You can try it that it works correctly by changing the URL manually in your browser.
✅ What happens if you enter an unknown path in the URL? How could we solve this?
## Adding navigation
## Adding Navigation
With routing established, users need a way to navigate through the app. Traditional websites reload entire pages when clicking links, but we want to update both the URL and content without page refreshes. This creates a smoother experience similar to how desktop applications switch between different views.
We need to coordinate two things: updating the browser's URL so users can bookmark pages and share links, and displaying the appropriate content. When implemented correctly, this creates the seamless navigation users expect from modern applications.
> 🏗️ **Architecture Insight**: Navigation System Components
>
> **What you're building:**
> - **🔄 URL Management**: Updates browser address bar without page reloads
> - **📋 Template System**: Swaps content dynamically based on current route
> - **📚 History Integration**: Maintains browser back/forward button functionality
> - **🛡️ Error Handling**: Graceful fallbacks for invalid or missing routes
>
> **How components work together:**
> - **Listens** for navigation events (clicks, history changes)
> - **Updates** the URL using the History API
> - **Renders** the appropriate template for the new route
> - **Maintains** a seamless user experience throughout
The next step for our app is to add the possibility to navigate between pages without having to change the URL manually. This implies two things:
@ -204,7 +370,7 @@ We already took care of the second part with the `updateRoute` function, so we h
We'll have to use JavaScript and more specifically the [`history.pushState`](https://developer.mozilla.org/docs/Web/API/History/pushState) that allows to update the URL and create a new entry in the browsing history, without reloading the HTML.
> Note: While the HTML anchor element [`<a href>`](https://developer.mozilla.org/docs/Web/HTML/Element/a) can be used on its own to create hyperlinks to different URLs, it will make the browser reload the HTML by default. It is necessary to prevent this behavior when handling routing with custom javascript, using the preventDefault() function on the click event.
> ⚠️ **Important Note**: While the HTML anchor element [`<a href>`](https://developer.mozilla.org/docs/Web/HTML/Element/a) can be used on its own to create hyperlinks to different URLs, it will make the browser reload the HTML by default. It is necessary to prevent this behavior when handling routing with custom javascript, using the preventDefault() function on the click event.
### Task
@ -217,6 +383,12 @@ function navigate(path) {
}
```
**Understanding this navigation function:**
- **Updates** the browser's URL to the new path using `history.pushState`
- **Adds** a new entry to the browser's history stack for proper back/forward button support
- **Triggers** the `updateRoute()` function to display the corresponding template
- **Maintains** the single-page app experience without page reloads
This method first updates the current URL based on the path given, then updates the template. The property `window.location.origin` returns the URL root, allowing us to reconstruct a complete URL from a given path.
Now that we have this function, we can take care of the problem we have if a path does not match any defined route. We'll modify the `updateRoute` function by adding a fallback to one of the existing route if we can't find a match.
@ -230,9 +402,20 @@ function updateRoute() {
return navigate('/login');
}
...
const template = document.getElementById(route.templateId);
const view = template.content.cloneNode(true);
const app = document.getElementById('app');
app.innerHTML = '';
app.appendChild(view);
}
```
**Key points to remember:**
- **Checks** if a route exists for the current path
- **Redirects** to the login page when an invalid route is accessed
- **Provides** a fallback mechanism that prevents broken navigation
- **Ensures** users always see a valid screen, even with incorrect URLs
If a route cannot be found, we'll now redirect to the `login` page.
Now let's create a function to get the URL when a link is clicked, and to prevent the browser's default link behavior:
@ -244,7 +427,11 @@ function onLinkClick(event) {
}
```
Let's complete the navigation system by adding bindings to our *Login* and *Logout* links in the HTML.
**Breaking down this click handler:**
- **Prevents** the browser's default link behavior using `preventDefault()`
- **Extracts** the destination URL from the clicked link element
- **Calls** our custom navigate function instead of reloading the page
- **Maintains** the smooth single-page application experience
```html
<a href="/dashboard" onclick="onLinkClick(event)">Login</a>
@ -252,15 +439,60 @@ Let's complete the navigation system by adding bindings to our *Login* and *Logo
<a href="/login" onclick="onLinkClick(event)">Logout</a>
```
The `event` object above, captures the `click` event and passes it to our `onLinkClick` function.
**What this onclick binding accomplishes:**
- **Connects** each link to our custom navigation system
- **Passes** the click event to our `onLinkClick` function for processing
- **Enables** smooth navigation without page reloads
- **Maintains** proper URL structure that users can bookmark or share
Using the [`onclick`](https://developer.mozilla.org/docs/Web/API/GlobalEventHandlers/onclick) attribute bind the `click` event to JavaScript code, here the call to the `navigate()` function.
The [`onclick`](https://developer.mozilla.org/docs/Web/API/GlobalEventHandlers/onclick) attribute bind the `click` event to JavaScript code, here the call to the `navigate()` function.
Try clicking on these links, you should be now able to navigate between the different screens of your app.
✅ The `history.pushState` method is part of the HTML5 standard and implemented in [all modern browsers](https://caniuse.com/?search=pushState). If you're building a web app for older browsers, there's a trick you can use in place of this API: using a [hash (`#`)](https://en.wikipedia.org/wiki/URI_fragment) before the path you can implement routing that works with regular anchor navigation and does not reload the page, as it's purpose was to create internal links within a page.
## Handling the browser's back and forward buttons
## Making the Back and Forward Buttons Work
The back and forward buttons are fundamental to web browsing, much like how NASA mission controllers can review previous system states during space missions. Users expect these buttons to work, and when they don't, it breaks the expected browsing experience.
Our single-page app needs additional configuration to support this. The browser maintains a history stack (which we've been adding to with `history.pushState`), but when users navigate through this history, our app needs to respond by updating the displayed content accordingly.
```mermaid
sequenceDiagram
participant User
participant Browser
participant App
participant Template
User->>Browser: Clicks "Login" link
Browser->>App: onclick event triggered
App->>App: preventDefault() & navigate('/dashboard')
App->>Browser: history.pushState('/dashboard')
Browser->>Browser: URL updates to /dashboard
App->>App: updateRoute() called
App->>Template: Find & clone dashboard template
Template->>App: Return cloned content
App->>Browser: Replace app content with template
Browser->>User: Display dashboard screen
Note over User,Template: User clicks browser back button
User->>Browser: Clicks back button
Browser->>Browser: History moves back to /login
Browser->>App: popstate event fired
App->>App: updateRoute() called automatically
App->>Template: Find & clone login template
Template->>App: Return cloned content
App->>Browser: Replace app content with template
Browser->>User: Display login screen
```
**Key interaction points:**
- **User actions** trigger navigation through clicks or browser buttons
- **App intercepts** link clicks to prevent page reloads
- **History API** manages URL changes and browser history stack
- **Templates** provide the content structure for each screen
- **Event listeners** ensure the app responds to all navigation types
Using the `history.pushState` creates new entries in the browser's navigation history. You can check that by holding the *back button* of your browser, it should display something like this:
@ -279,7 +511,14 @@ window.onpopstate = () => updateRoute();
updateRoute();
```
> Note: we used an [arrow function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Functions/Arrow_functions) here to declare our `popstate` event handler for conciseness, but a regular function would work the same.
**Understanding this history integration:**
- **Listens** for `popstate` events that occur when users navigate with browser buttons
- **Uses** an arrow function for concise event handler syntax
- **Calls** `updateRoute()` automatically whenever the history state changes
- **Initializes** the app by calling `updateRoute()` when the page first loads
- **Ensures** the correct template displays regardless of how users navigate
> 💡 **Pro Tip**: We used an [arrow function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Functions/Arrow_functions) here to declare our `popstate` event handler for conciseness, but a regular function would work the same.
Here's a refresher video on arrow functions:
@ -299,10 +538,18 @@ Use the Agent mode to complete the following challenge:
**Prompt:** Create a new HTML template with id "not-found" that displays a user-friendly 404 error page with styling. Then modify the JavaScript routing logic to show this template when users navigate to invalid URLs, and add a "Go Home" button that navigates back to the login page.
Learn more about [agent mode](https://code.visualstudio.com/blogs/2025/02/24/introducing-copilot-agent-mode) here.
## 🚀 Challenge
Add a new template and route for a third page that shows the credits for this app.
**Challenge goals:**
- **Create** a new HTML template with appropriate content structure
- **Add** the new route to your routes configuration object
- **Include** navigation links to and from the credits page
- **Test** that all navigation works correctly with browser history
## Post-Lecture Quiz
[Post-lecture quiz](https://ff-quizzes.netlify.app/web/quiz/42)
@ -311,6 +558,12 @@ Add a new template and route for a third page that shows the credits for this ap
Routing is one of the surprisingly tricky parts of web development, especially as the web moves from page refresh behaviors to Single Page Application page refreshes. Read a little about [how the Azure Static Web App service](https://docs.microsoft.com/azure/static-web-apps/routes/?WT.mc_id=academic-77807-sagibbon) handles routing. Can you explain why some of the decisions described on that document are necessary?
**Additional learning resources:**
- **Explore** how popular frameworks like React Router and Vue Router implement client-side routing
- **Research** the differences between hash-based routing and history API routing
- **Learn** about server-side rendering (SSR) and how it affects routing strategies
- **Investigate** how Progressive Web Apps (PWAs) handle routing and navigation
## Assignment
[Improve the routing](assignment.md)

@ -1,11 +1,41 @@
# Improve the routing
# Improve the Routing
## Instructions
Now that you've built a basic routing system, it's time to enhance it with professional features that improve user experience and provide better developer tools. Real-world applications need more than just template switching \u2013 they require dynamic page titles, lifecycle hooks, and extensible architectures.
In this assignment, you'll extend your routing implementation with two essential features that are commonly found in production web applications. These enhancements will make your banking app feel more polished and provide a foundation for future functionality.
The routes declaration contains currently only the template ID to use. But when displaying a new page, a bit more is needed sometimes. Let's improve our routing implementation with two additional features:
- Give titles to each template and update the window title with this new title when the template changes.
- Add an option to run some code after the template changes. We want to print `'Dashboard is shown'` in the developer console every time the dashboard page is displayed.
### Feature 1: Dynamic Page Titles
**Objective:** Give titles to each template and update the window title with this new title when the template changes.
**Why this matters:**
- **Improves** user experience by showing descriptive browser tab titles
- **Enhances** accessibility for screen readers and assistive technologies
- **Provides** better bookmarking and browser history context
- **Follows** professional web development best practices
**Implementation approach:**
- **Extend** the routes object to include title information for each route
- **Modify** the `updateRoute()` function to update `document.title` dynamically
- **Test** that titles change correctly when navigating between screens
### Feature 2: Route Lifecycle Hooks
**Objective:** Add an option to run some code after the template changes. We want to print `'Dashboard is shown'` in the developer console every time the dashboard page is displayed.
**Why this matters:**
- **Enables** custom logic execution when specific routes load
- **Provides** hooks for analytics, logging, or initialization code
- **Creates** a foundation for more complex route behaviors
- **Demonstrates** the observer pattern in web development
**Implementation approach:**
- **Add** an optional callback function property to route configurations
- **Execute** the callback function (if present) after template rendering completes
- **Ensure** the feature works for any route with a defined callback
- **Test** that the console message appears when visiting the dashboard
## Rubric

@ -4,279 +4,732 @@
[Pre-lecture quiz](https://ff-quizzes.netlify.app/web/quiz/43)
### Introduction
Ever filled out a form online and had it reject your email format? Or lost all your information when you clicked submit? We've all encountered these frustrating experiences.
In almost all modern web apps, you can create an account to have your own private space. As multiple users can access a web app at the same time, you need a mechanism to store each user personal data separately and select which information to display information. We won't cover how to manage [user identity securely](https://en.wikipedia.org/wiki/Authentication) as it's an extensive topic on its own, but we'll make sure each user is able to create one (or more) bank account on our app.
Forms are the bridge between your users and your application's functionality. Like the careful protocols that air traffic controllers use to guide planes safely to their destinations, well-designed forms provide clear feedback and prevent costly errors. Poor forms, on the other hand, can drive users away faster than a miscommunication in a busy airport.
In this part we'll use HTML forms to add login and registration to our web app. We'll see how to send the data to a server API programmatically, and ultimately how to define basic validation rules for user inputs.
In this lesson, we'll transform your static banking app into an interactive application. You'll learn to build forms that validate user input, communicate with servers, and provide helpful feedback. Think of it as building the control interface that lets users navigate your application's features.
### Prerequisite
By the end, you'll have a complete login and registration system with validation that guides users toward success rather than frustration.
You need to have completed the [HTML templates and routing](../1-template-route/README.md) of the web app for this lesson. You also need to install [Node.js](https://nodejs.org) and [run the server API](../api/README.md) locally so you can send data to create accounts.
## Prerequisites
**Take note**
You will have two terminals running at the same time as listed below.
1. For the main bank app we built in the [HTML templates and routing](../1-template-route/README.md) lesson
2. For the [Bank APP server API](../api/README.md) we just setup above.
Before we start building forms, let's make sure you've got everything set up correctly. This lesson picks up right where we left off in the previous one, so if you skipped ahead, you might want to go back and get the basics working first.
You need two of the servers up and running to follow through with the rest of the lesson. They are listening on different ports(port `3000` and port `5000`) so everything should work just fine.
### Required Setup
You can test that the server is running properly by executing this command in a terminal:
| Component | Status | Description |
|-----------|--------|-------------|
| [HTML Templates](../1-template-route/README.md) | ✅ Required | Your basic banking app structure |
| [Node.js](https://nodejs.org) | ✅ Required | JavaScript runtime for the server |
| [Bank API Server](../api/README.md) | ✅ Required | Backend service for data storage |
```sh
> 💡 **Development Tip**: You'll be running two separate servers simultaneously one for your front-end banking app and another for the backend API. This setup mirrors real-world development where frontend and backend services operate independently.
### Server Configuration
**Your development environment will include:**
- **Frontend server**: Serves your banking app (typically port `3000`)
- **Backend API server**: Handles data storage and retrieval (port `5000`)
- **Both servers** can run simultaneously without conflicts
**Testing your API connection:**
```bash
curl http://localhost:5000/api
# -> should return "Bank API v1.0.0" as a result
# Expected response: "Bank API v1.0.0"
```
**If you see the API version response, you're ready to proceed!**
---
## Form and controls
## Understanding HTML Forms and Controls
HTML forms are how users communicate with your web application. Think of them as the telegraph system that connected distant places in the 19th century they're the communication protocol between user intent and application response. When designed thoughtfully, they catch errors, guide input formatting, and provide helpful suggestions.
The `<form>` element encapsulates a section of an HTML document where the user can input and submit data with interactive controls. There are all sorts of user interface (UI) controls that can be used within a form, the most common one being the `<input>` and the `<button>` elements.
Modern forms are significantly more sophisticated than basic text inputs. HTML5 introduced specialized input types that handle email validation, number formatting, and date selection automatically. These improvements benefit both accessibility and mobile user experiences.
There are a lot of different [types](https://developer.mozilla.org/docs/Web/HTML/Element/input) of `<input>`, for example to create a field where the user can enter its username you can use:
### Essential Form Elements
**Building blocks every form needs:**
```html
<input id="username" name="username" type="text">
<!-- Basic form structure -->
<form id="userForm" method="POST">
<label for="username">Username</label>
<input id="username" name="username" type="text" required>
<button type="submit">Submit</button>
</form>
```
The `name` attribute will be used as the property name when the form data will be sent over. The `id` attribute is used to associate a `<label>` with the form control.
**Here's what this code does:**
- **Creates** a form container with a unique identifier
- **Specifies** the HTTP method for data submission
- **Associates** labels with inputs for accessibility
- **Defines** a submit button to process the form
### Modern Input Types and Attributes
| Input Type | Purpose | Example Usage |
|------------|---------|---------------|
| `text` | General text input | `<input type="text" name="username">` |
| `email` | Email validation | `<input type="email" name="email">` |
| `password` | Hidden text entry | `<input type="password" name="password">` |
| `number` | Numeric input | `<input type="number" name="balance" min="0">` |
| `tel` | Phone numbers | `<input type="tel" name="phone">` |
> 💡 **Modern HTML5 Advantage**: Using specific input types provides automatic validation, appropriate mobile keyboards, and better accessibility support without additional JavaScript!
> Take a look at the whole list of [`<input>` types](https://developer.mozilla.org/docs/Web/HTML/Element/input) and [other form controls](https://developer.mozilla.org/docs/Learn/Forms/Other_form_controls) to get an idea of all the native UI elements you can use when building your UI.
### Button Types and Behavior
✅ Note that `<input>` is an [empty element](https://developer.mozilla.org/docs/Glossary/Empty_element) on which you should *not* add a matching closing tag. You can however use the self-closing `<input/>` notation, but it's not required.
```html
<!-- Different button behaviors -->
<button type="submit">Save Data</button> <!-- Submits the form -->
<button type="reset">Clear Form</button> <!-- Resets all fields -->
<button type="button">Custom Action</button> <!-- No default behavior -->
```
The `<button>` element within a form is a bit special. If you do not specify its `type` attribute, it will automatically submit the form data to the server when pressed. Here are the possible `type` values:
**Here's what each button type does:**
- **Submit buttons**: Trigger form submission and send data to the specified endpoint
- **Reset buttons**: Restore all form fields to their initial state
- **Regular buttons**: Provide no default behavior, requiring custom JavaScript for functionality
- `submit`: The default within a `<form>`, the button triggers the form submit action.
- `reset`: The button resets all the form controls to their initial values.
- `button`: Do not assign a default behavior when the button is pressed. You can then assign custom actions to it using JavaScript.
> ⚠️ **Important Note**: The `<input>` element is self-closing and doesn't require a closing tag. Modern best practice is to write `<input>` without the slash.
### Task
### Building Your Login Form
Let's start by adding a form to the `login` template. We'll need a *username* field and a *Login* button.
Now let's create a practical login form that demonstrates modern HTML form practices. We'll start with a basic structure and gradually enhance it with accessibility features and validation.
```html
<template id="login">
<h1>Bank App</h1>
<section>
<h2>Login</h2>
<form id="loginForm">
<label for="username">Username</label>
<input id="username" name="user" type="text">
<button>Login</button>
<form id="loginForm" novalidate>
<div class="form-group">
<label for="username">Username</label>
<input id="username" name="user" type="text" required
autocomplete="username" placeholder="Enter your username">
</div>
<button type="submit">Login</button>
</form>
</section>
</template>
```
If you take a closer look, you can notice that we also added a `<label>` element here. `<label>` elements are used to add a name to UI controls, such as our username field. Labels are important for the readability of your forms, but also come with additional benefits:
**Breaking down what happens here:**
- **Structures** the form with semantic HTML5 elements
- **Groups** related elements using `div` containers with meaningful classes
- **Associates** labels with inputs using the `for` and `id` attributes
- **Includes** modern attributes like `autocomplete` and `placeholder` for better UX
- **Adds** `novalidate` to handle validation with JavaScript instead of browser defaults
### The Power of Proper Labels
**Why labels matter for modern web development:**
```mermaid
graph TD
A[Label Element] --> B[Screen Reader Support]
A --> C[Click Target Expansion]
A --> D[Form Validation]
A --> E[SEO Benefits]
B --> F[Accessible to all users]
C --> G[Better mobile experience]
D --> H[Clear error messaging]
E --> I[Better search ranking]
```
- By associating a label to a form control, it helps users using assistive technologies (like a screen reader) to understand what data they're expected to provide.
- You can click on the label to directly put focus on the associated input, making it easier to reach on touch-screen based devices.
**What proper labels accomplish:**
- **Enables** screen readers to announce form fields clearly
- **Expands** the clickable area (clicking the label focuses the input)
- **Improves** mobile usability with larger touch targets
- **Supports** form validation with meaningful error messages
- **Enhances** SEO by providing semantic meaning to form elements
> [Accessibility](https://developer.mozilla.org/docs/Learn/Accessibility/What_is_accessibility) on the web is a very important topic that's often overlooked. Thanks to [semantic HTML elements](https://developer.mozilla.org/docs/Learn/Accessibility/HTML) it's not difficult to create accessible content if you use them properly. You can [read more about accessibility](https://developer.mozilla.org/docs/Web/Accessibility) to avoid common mistakes and become a responsible developer.
> 🎯 **Accessibility Goal**: Every form input should have an associated label. This simple practice makes your forms usable by everyone, including users with disabilities, and improves the experience for all users.
Now we'll add a second form for the registration, just below the previous one:
### Creating the Registration Form
The registration form requires more detailed information to create a complete user account. Let's build it with modern HTML5 features and enhanced accessibility.
```html
<hr/>
<h2>Register</h2>
<form id="registerForm">
<label for="user">Username</label>
<input id="user" name="user" type="text">
<label for="currency">Currency</label>
<input id="currency" name="currency" type="text" value="$">
<label for="description">Description</label>
<input id="description" name="description" type="text">
<label for="balance">Current balance</label>
<input id="balance" name="balance" type="number" value="0">
<button>Register</button>
<form id="registerForm" novalidate>
<div class="form-group">
<label for="user">Username</label>
<input id="user" name="user" type="text" required
autocomplete="username" placeholder="Choose a username">
</div>
<div class="form-group">
<label for="currency">Currency</label>
<input id="currency" name="currency" type="text" value="$"
required maxlength="3" placeholder="USD, EUR, etc.">
</div>
<div class="form-group">
<label for="description">Account Description</label>
<input id="description" name="description" type="text"
maxlength="100" placeholder="Personal savings, checking, etc.">
</div>
<div class="form-group">
<label for="balance">Starting Balance</label>
<input id="balance" name="balance" type="number" value="0"
min="0" step="0.01" placeholder="0.00">
</div>
<button type="submit">Create Account</button>
</form>
```
Using the `value` attribute we can define a default value for a given input.
Notice also that the input for `balance` has the `number` type. Does it look different than the other inputs? Try interacting with it.
**In the above, we've:**
- **Organized** each field in container divs for better styling and layout
- **Added** appropriate `autocomplete` attributes for browser autofill support
- **Included** helpful placeholder text to guide user input
- **Set** sensible defaults using the `value` attribute
- **Applied** validation attributes like `required`, `maxlength`, and `min`
- **Used** `type="number"` for the balance field with decimal support
### Exploring Input Types and Behavior
**Modern input types provide enhanced functionality:**
| Feature | Benefit | Example |
|---------|---------|----------|
| `type="number"` | Numeric keypad on mobile | Easier balance entry |
| `step="0.01"` | Decimal precision control | Allows cents in currency |
| `autocomplete` | Browser autofill | Faster form completion |
| `placeholder` | Contextual hints | Guides user expectations |
> 🎯 **Accessibility Challenge**: Try navigating the forms using only your keyboard! Use `Tab` to move between fields, `Space` to check boxes, and `Enter` to submit. This experience helps you understand how screen reader users interact with your forms.
## Understanding Form Submission Methods
When someone fills out your form and hits submit, that data needs to go somewhere usually to a server that can save it. There are a couple of different ways this can happen, and knowing which one to use can save you from some headaches later.
✅ Can you navigate and interact with the forms using only a keyboard? How would you do that?
Let's take a look at what actually happens when someone clicks that submit button.
## Submitting data to the server
### Default Form Behavior
Now that we have a functional UI, the next step is to send the data over to our server. Let's make a quick test using our current code: what happens if you click on the *Login* or *Register* button?
First, let's observe what happens with basic form submission:
Did you notice the change in your browser's URL section?
**Test your current forms:**
1. Click the *Register* button in your form
2. Observe the changes in your browser's address bar
3. Notice how the page reloads and data appears in the URL
![Screenshot of the browser's URL change after clicking the Register button](./images/click-register.png)
The default action for a `<form>` is to submit the form to the current server URL using the [GET method](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3), appending the form data directly to the URL. This method has some shortcomings though:
### HTTP Methods Comparison
```mermaid
graph TD
A[Form Submission] --> B{HTTP Method}
B -->|GET| C[Data in URL]
B -->|POST| D[Data in Request Body]
C --> E[Visible in address bar]
C --> F[Limited data size]
C --> G[Bookmarkable]
D --> H[Hidden from URL]
D --> I[Large data capacity]
D --> J[More secure]
```
**Understanding the differences:**
- The data sent is very limited in size (about 2000 characters)
- The data is directly visible in the URL (not great for passwords)
- It does not work with file uploads
| Method | Use Case | Data Location | Security Level | Size Limit |
|--------|----------|---------------|----------------|-------------|
| `GET` | Search queries, filters | URL parameters | Low (visible) | ~2000 characters |
| `POST` | User accounts, sensitive data | Request body | Higher (hidden) | No practical limit |
That's why you can change it to use the [POST method](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5) which sends the form data to the server in the body of the HTTP request, without any of the previous limitations.
**Understanding the fundamental differences:**
- **GET**: Appends form data to the URL as query parameters (appropriate for search operations)
- **POST**: Includes data in the request body (essential for sensitive information)
- **GET limitations**: Size constraints, visible data, persistent browser history
- **POST advantages**: Large data capacity, privacy protection, file upload support
> While POST is the most commonly used method to send data over, [in some specific scenarios](https://www.w3.org/2001/tag/doc/whenToUseGet.html) it is preferable to use the GET method, when implementing a search field for example.
> 💡 **Best Practice**: Use `GET` for search forms and filters (data retrieval), use `POST` for user registration, login, and data creation.
### Task
### Configuring Form Submission
Add `action` and `method` properties to the registration form:
Let's configure your registration form to communicate properly with the backend API using the POST method:
```html
<form id="registerForm" action="//localhost:5000/api/accounts" method="POST">
<form id="registerForm" action="//localhost:5000/api/accounts"
method="POST" novalidate>
```
Now try to register a new account with your name. After clicking on the *Register* button you should see something like this:
**Here's what this configuration does:**
- **Directs** form submission to your API endpoint
- **Uses** POST method for secure data transmission
- **Includes** `novalidate` to handle validation with JavaScript
### Testing Form Submission
**Follow these steps to test your form:**
1. **Fill out** the registration form with your information
2. **Click** the "Create Account" button
3. **Observe** the server response in your browser
![A browser window at the address localhost:5000/api/accounts, showing a JSON string with user data](./images/form-post.png)
If everything goes well, the server should answer your request with a [JSON](https://www.json.org/json-en.html) response containing the account data that was created.
**What you should see:**
- **Browser redirects** to the API endpoint URL
- **JSON response** containing your newly created account data
- **Server confirmation** that the account was successfully created
> 🧪 **Experiment Time**: Try registering again with the same username. What response do you get? This helps you understand how the server handles duplicate data and error conditions.
### Understanding JSON Responses
**When the server processes your form successfully:**
```json
{
"user": "john_doe",
"currency": "$",
"description": "Personal savings",
"balance": 100,
"id": "unique_account_id"
}
```
**This response confirms:**
- **Creates** a new account with your specified data
- **Assigns** a unique identifier for future reference
- **Returns** all account information for verification
- **Indicates** successful database storage
✅ Try registering again with the same name. What happens?
## Modern Form Handling with JavaScript
## Submitting data without reloading the page
Traditional form submissions cause full page reloads, similar to how early space missions required complete system resets for course corrections. This approach disrupts the user experience and loses application state.
As you probably noticed, there's a slight issue with the approach we just used: when submitting the form, we get out of our app and the browser redirects to the server URL. We're trying to avoid all page reloads with our web app, as we're making a [Single-page application (SPA)](https://en.wikipedia.org/wiki/Single-page_application).
JavaScript form handling works like the continuous guidance systems used by modern spacecraft making real-time adjustments without losing navigation context. We can intercept form submissions, provide immediate feedback, handle errors gracefully, and update the interface based on server responses while maintaining the user's position in the application.
To send the form data to the server without forcing a page reload, we have to use JavaScript code. Instead of putting an URL in the `action` property of a `<form>` element, you can use any JavaScript code prepended by the `javascript:` string to perform a custom action. Using this also means that you'll have to implement some tasks that were previously done automatically by the browser:
### Why Avoid Page Reloads?
- Retrieve the form data
- Convert and encode the form data to a suitable format
- Create the HTTP request and send it to the server
```mermaid
sequenceDiagram
participant User
participant SPA
participant Server
User->>SPA: Submits form
SPA->>Server: AJAX request
Server-->>SPA: JSON response
SPA->>User: Updates interface
Note over User,SPA: No page reload!
```
**Benefits of JavaScript form handling:**
- **Maintains** application state and user context
- **Provides** instant feedback and loading indicators
- **Enables** dynamic error handling and validation
- **Creates** smooth, app-like user experiences
- **Allows** conditional logic based on server responses
### Transitioning from Traditional to Modern Forms
**Traditional approach challenges:**
- **Redirects** users away from your application
- **Loses** current application state and context
- **Requires** full page reloads for simple operations
- **Provides** limited control over user feedback
**Modern JavaScript approach advantages:**
- **Keeps** users within your application
- **Maintains** all application state and data
- **Enables** real-time validation and feedback
- **Supports** progressive enhancement and accessibility
### Task
### Implementing JavaScript Form Handling
Replace the registration form `action` with:
Let's replace the traditional form submission with modern JavaScript event handling:
```html
<form id="registerForm" action="javascript:register()">
<!-- Remove the action attribute and add event handling -->
<form id="registerForm" method="POST" novalidate>
```
Open `app.js` add a new function named `register`:
**Add the registration logic to your `app.js` file:**
```js
```javascript
// Modern event-driven form handling
function register() {
const registerForm = document.getElementById('registerForm');
const formData = new FormData(registerForm);
const data = Object.fromEntries(formData);
const jsonData = JSON.stringify(data);
console.log('Form data prepared:', data);
}
// Attach event listener when the page loads
document.addEventListener('DOMContentLoaded', () => {
const registerForm = document.getElementById('registerForm');
registerForm.addEventListener('submit', (event) => {
event.preventDefault(); // Prevent default form submission
register();
});
});
```
Here we retrieve the form element using `getElementById()` and use the [`FormData`](https://developer.mozilla.org/docs/Web/API/FormData) helper to extract the values from form controls as a set of key/value pairs. Then we convert the data to a regular object using [`Object.fromEntries()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/fromEntries) and finally serialize the data to [JSON](https://www.json.org/json-en.html), a format commonly used for exchanging data on the web.
**Breaking down what happens here:**
- **Prevents** default form submission using `event.preventDefault()`
- **Retrieves** the form element using modern DOM selection
- **Extracts** form data using the powerful `FormData` API
- **Converts** FormData to a plain object with `Object.fromEntries()`
- **Serializes** the data to JSON format for server communication
- **Logs** the processed data for debugging and verification
### Understanding the FormData API
**The FormData API provides powerful form handling:**
```javascript
// Example of what FormData captures
const formData = new FormData(registerForm);
// FormData automatically captures:
// {
// "user": "john_doe",
// "currency": "$",
// "description": "Personal account",
// "balance": "100"
// }
```
**FormData API advantages:**
- **Comprehensive collection**: Captures all form elements including text, files, and complex inputs
- **Type awareness**: Handles different input types automatically without custom coding
- **Efficiency**: Eliminates manual field collection with single API call
- **Adaptability**: Maintains functionality as form structure evolves
### Creating the Server Communication Function
The data is now ready to be sent to the server. Create a new function named `createAccount`:
Now let's build a robust function to communicate with your API server using modern JavaScript patterns:
```js
```javascript
async function createAccount(account) {
try {
const response = await fetch('//localhost:5000/api/accounts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: account
});
// Check if the response was successful
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
return { error: error.message || 'Unknown error' };
console.error('Account creation failed:', error);
return { error: error.message || 'Network error occurred' };
}
}
```
What's this function doing? First, notice the `async` keyword here. This means that the function contains code that will execute [**asynchronously**](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/async_function). When used along the `await` keyword, it allows waiting for asynchronous code to execute - like waiting for the server response here - before continuing.
Here's a quick video about `async/await` usage:
[![Async and Await for managing promises](https://img.youtube.com/vi/YwmlRkrxvkk/0.jpg)](https://youtube.com/watch?v=YwmlRkrxvkk "Async and Await for managing promises")
> 🎥 Click the image above for a video about async/await.
**Understanding asynchronous JavaScript:**
```mermaid
sequenceDiagram
participant JS as JavaScript
participant Fetch as Fetch API
participant Server as Backend Server
JS->>Fetch: fetch() request
Fetch->>Server: HTTP POST
Server-->>Fetch: JSON response
Fetch-->>JS: await response
JS->>JS: Process data
```
We use the `fetch()` API to send JSON data to the server. This method takes 2 parameters:
**What this modern implementation accomplishes:**
- **Uses** `async/await` for readable asynchronous code
- **Includes** proper error handling with try/catch blocks
- **Checks** response status before processing data
- **Sets** appropriate headers for JSON communication
- **Provides** detailed error messages for debugging
- **Returns** consistent data structure for success and error cases
- The URL of the server, so we put back `//localhost:5000/api/accounts` here.
- The settings of the request. That's where we set the method to `POST` and provide the `body` for the request. As we're sending JSON data to the server, we also need to set the `Content-Type` header to `application/json` so the server knows how to interpret the content.
### The Power of Modern Fetch API
As the server will respond to the request with JSON, we can use `await response.json()` to parse the JSON content and return the resulting object. Note that this method is asynchronous, so we use the `await` keyword here before returning to make sure any errors during parsing are also caught.
**Fetch API advantages over older methods:**
Now add some code to the `register` function to call `createAccount()`:
| Feature | Benefit | Implementation |
|---------|---------|----------------|
| Promise-based | Clean async code | `await fetch()` |
| Request customization | Full HTTP control | Headers, methods, body |
| Response handling | Flexible data parsing | `.json()`, `.text()`, `.blob()` |
| Error handling | Comprehensive error catching | Try/catch blocks |
```js
const result = await createAccount(jsonData);
```
> 🎥 **Learn More**: [Async/Await Tutorial](https://youtube.com/watch?v=YwmlRkrxvkk) - Understanding asynchronous JavaScript patterns for modern web development.
Because we use the `await` keyword here, we need to add the `async` keyword before the register function:
**Key concepts for server communication:**
- **Async functions** allow pausing execution to wait for server responses
- **Await keyword** makes asynchronous code read like synchronous code
- **Fetch API** provides modern, promise-based HTTP requests
- **Error handling** ensures your app responds gracefully to network issues
```js
async function register() {
```
### Completing the Registration Function
Finally, let's add some logs to check the result. The final function should look like this:
Let's bring everything together with a complete, production-ready registration function:
```js
```javascript
async function register() {
const registerForm = document.getElementById('registerForm');
const formData = new FormData(registerForm);
const jsonData = JSON.stringify(Object.fromEntries(formData));
const result = await createAccount(jsonData);
if (result.error) {
return console.log('An error occurred:', result.error);
const submitButton = registerForm.querySelector('button[type="submit"]');
try {
// Show loading state
submitButton.disabled = true;
submitButton.textContent = 'Creating Account...';
// Process form data
const formData = new FormData(registerForm);
const jsonData = JSON.stringify(Object.fromEntries(formData));
// Send to server
const result = await createAccount(jsonData);
if (result.error) {
console.error('Registration failed:', result.error);
alert(`Registration failed: ${result.error}`);
return;
}
console.log('Account created successfully!', result);
alert(`Welcome, ${result.user}! Your account has been created.`);
// Reset form after successful registration
registerForm.reset();
} catch (error) {
console.error('Unexpected error:', error);
alert('An unexpected error occurred. Please try again.');
} finally {
// Restore button state
submitButton.disabled = false;
submitButton.textContent = 'Create Account';
}
console.log('Account created!', result);
}
```
That was a bit long but we got there! If you open your [browser developer tools](https://developer.mozilla.org/docs/Learn/Common_questions/What_are_browser_developer_tools), and try registering a new account, you should not see any change on the web page but a message will appear in the console confirming that everything works.
**This enhanced implementation includes:**
- **Provides** visual feedback during form submission
- **Disables** the submit button to prevent duplicate submissions
- **Handles** both expected and unexpected errors gracefully
- **Shows** user-friendly success and error messages
- **Resets** the form after successful registration
- **Restores** UI state regardless of outcome
### Testing Your Implementation
**Open your browser developer tools and test the registration:**
1. **Open** the browser console (F12 → Console tab)
2. **Fill out** the registration form
3. **Click** "Create Account"
4. **Observe** the console messages and user feedback
![Screenshot showing log message in the browser console](./images/browser-console.png)
✅ Do you think the data is sent to the server securely? What if someone what was able to intercept the request? You can read about [HTTPS](https://en.wikipedia.org/wiki/HTTPS) to know more about secure data communication.
**What you should see:**
- **Loading state** appears on the submit button
- **Console logs** show detailed information about the process
- **Success message** appears when account creation succeeds
- **Form resets** automatically after successful submission
## Data validation
> 🔒 **Security Consideration**: Currently, data travels over HTTP, which is not secure for production. In real applications, always use HTTPS to encrypt data transmission. Learn more about [HTTPS security](https://en.wikipedia.org/wiki/HTTPS) and why it's essential for protecting user data.
If you try to register a new account without setting an username first, you can see that the server returns an error with status code [400 (Bad Request)](https://developer.mozilla.org/docs/Web/HTTP/Status/400#:~:text=The%20HyperText%20Transfer%20Protocol%20(HTTP,%2C%20or%20deceptive%20request%20routing).).
## Comprehensive Form Validation
Before sending data to a server it's a good practice to [validate the form data](https://developer.mozilla.org/docs/Learn/Forms/Form_validation) beforehand when possible, to make sure you send a valid request. HTML5 forms controls provides built-in validation using various attributes:
Form validation prevents the frustrating experience of discovering errors only after submission. Like the multiple redundant systems on the International Space Station, effective validation employs multiple layers of safety checks.
- `required`: the field needs to be filled otherwise the form cannot be submitted.
- `minlength` and `maxlength`: defines the minimum and maximum number of characters in text fields.
- `min` and `max`: defines the minimum and maximum value of a numerical field.
- `type`: defines the kind of data expected, like `number`, `email`, `file` or [other built-in types](https://developer.mozilla.org/docs/Web/HTML/Element/input). This attribute may also change the visual rendering of the form control.
- `pattern`: allows to define a [regular expression](https://developer.mozilla.org/docs/Web/JavaScript/Guide/Regular_Expressions) pattern to test if the entered data is valid or not.
The optimal approach combines browser-level validation for immediate feedback, JavaScript validation for enhanced user experience, and server-side validation for security and data integrity. This redundancy ensures both user satisfaction and system protection.
> Tip: you can customize the look of your form controls depending if they're valid or not using the `:valid` and `:invalid` CSS pseudo-classes.
### Understanding Validation Layers
### Task
```mermaid
graph TD
A[User Input] --> B[HTML5 Validation]
B --> C[Custom JavaScript Validation]
C --> D[Client-Side Complete]
D --> E[Server-Side Validation]
E --> F[Data Storage]
B -->|Invalid| G[Browser Error Message]
C -->|Invalid| H[Custom Error Display]
E -->|Invalid| I[Server Error Response]
```
There are 2 required fields to create a valid new account, the username and currency, the other fields being optional. Update the form's HTML, using both the `required` attribute and text in the field's label to that:
**Multi-layer validation strategy:**
- **HTML5 validation**: Immediate browser-based checks
- **JavaScript validation**: Custom logic and user experience
- **Server validation**: Final security and data integrity checks
- **Progressive enhancement**: Works even if JavaScript is disabled
```html
<label for="user">Username (required)</label>
<input id="user" name="user" type="text" required>
...
<label for="currency">Currency (required)</label>
<input id="currency" name="currency" type="text" value="$" required>
### HTML5 Validation Attributes
**Modern validation tools at your disposal:**
| Attribute | Purpose | Example Usage | Browser Behavior |
|-----------|---------|---------------|------------------|
| `required` | Mandatory fields | `<input required>` | Prevents empty submission |
| `minlength`/`maxlength` | Text length limits | `<input maxlength="20">` | Enforces character limits |
| `min`/`max` | Numeric ranges | `<input min="0" max="1000">` | Validates number bounds |
| `pattern` | Custom regex rules | `<input pattern="[A-Za-z]+">` | Matches specific formats |
| `type` | Data type validation | `<input type="email">` | Format-specific validation |
### CSS Validation Styling
**Create visual feedback for validation states:**
```css
/* Valid input styling */
input:valid {
border-color: #28a745;
background-color: #f8fff9;
}
/* Invalid input styling */
input:invalid {
border-color: #dc3545;
background-color: #fff5f5;
}
/* Focus states for better accessibility */
input:focus:valid {
box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);
}
input:focus:invalid {
box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
}
```
While this particular server implementation does not enforce specific limits on the fields maximum length, it's always a good practice to define reasonable limits for any user text entry.
**What these visual cues accomplish:**
- **Green borders**: Indicate successful validation, like green lights in mission control
- **Red borders**: Signal validation errors requiring attention
- **Focus highlights**: Provide clear visual context for current input location
- **Consistent styling**: Establish predictable interface patterns users can learn
> 💡 **Pro Tip**: Use the `:valid` and `:invalid` CSS pseudo-classes to provide immediate visual feedback as users type, creating a responsive and helpful interface.
Add a `maxlength` attribute to the text fields:
### Implementing Comprehensive Validation
Let's enhance your registration form with robust validation that provides excellent user experience and data quality:
```html
<input id="user" name="user" type="text" maxlength="20" required>
...
<input id="currency" name="currency" type="text" value="$" maxlength="5" required>
...
<input id="description" name="description" type="text" maxlength="100">
<form id="registerForm" method="POST" novalidate>
<div class="form-group">
<label for="user">Username <span class="required">*</span></label>
<input id="user" name="user" type="text" required
minlength="3" maxlength="20"
pattern="[a-zA-Z0-9_]+"
autocomplete="username"
title="Username must be 3-20 characters, letters, numbers, and underscores only">
<small class="form-text">Choose a unique username (3-20 characters)</small>
</div>
<div class="form-group">
<label for="currency">Currency <span class="required">*</span></label>
<input id="currency" name="currency" type="text" required
value="$" maxlength="3"
pattern="[A-Z$€£¥₹]+"
title="Enter a valid currency symbol or code">
<small class="form-text">Currency symbol (e.g., $, €, £)</small>
</div>
<div class="form-group">
<label for="description">Account Description</label>
<input id="description" name="description" type="text"
maxlength="100"
placeholder="Personal savings, checking, etc.">
<small class="form-text">Optional description (up to 100 characters)</small>
</div>
<div class="form-group">
<label for="balance">Starting Balance</label>
<input id="balance" name="balance" type="number"
value="0" min="0" step="0.01"
title="Enter a positive number for your starting balance">
<small class="form-text">Initial account balance (minimum $0.00)</small>
</div>
<button type="submit">Create Account</button>
</form>
```
Now if you press the *Register* button and a field does not respect a validation rule we defined, you should see something like this:
**Understanding the enhanced validation:**
- **Combines** required field indicators with helpful descriptions
- **Includes** `pattern` attributes for format validation
- **Provides** `title` attributes for accessibility and tooltips
- **Adds** helper text to guide user input
- **Uses** semantic HTML structure for better accessibility
### Advanced Validation Rules
**What each validation rule accomplishes:**
| Field | Validation Rules | User Benefit |
|-------|------------------|--------------|
| Username | `required`, `minlength="3"`, `maxlength="20"`, `pattern="[a-zA-Z0-9_]+"` | Ensures valid, unique identifiers |
| Currency | `required`, `maxlength="3"`, `pattern="[A-Z$€£¥₹]+"` | Accepts common currency symbols |
| Balance | `min="0"`, `step="0.01"`, `type="number"` | Prevents negative balances |
| Description | `maxlength="100"` | Reasonable length limits |
### Testing Validation Behavior
**Try these validation scenarios:**
1. **Submit** the form with empty required fields
2. **Enter** a username shorter than 3 characters
3. **Try** special characters in the username field
4. **Input** a negative balance amount
![Screenshot showing the validation error when trying to submit the form](./images/validation-error.png)
Validation like this performed *before* sending any data to the server is called **client-side** validation. But note that it's not always possible to perform all checks without sending the data. For example, we cannot check here if an account already exists with the same username without sending a request to the server. Additional validation performed on the server is called **server-side** validation.
**What you'll observe:**
- **Browser displays** native validation messages
- **Styling changes** based on `:valid` and `:invalid` states
- **Form submission** is prevented until all validations pass
- **Focus automatically** moves to the first invalid field
### Client-Side vs Server-Side Validation
```mermaid
graph LR
A[Client-Side Validation] --> B[Instant Feedback]
A --> C[Better UX]
A --> D[Reduced Server Load]
E[Server-Side Validation] --> F[Security]
E --> G[Data Integrity]
E --> H[Business Rules]
A -.-> I[Both Required]
E -.-> I
```
Usually both need to be implemented, and while using client-side validation improves the user experience by providing instant feedback to the user, server-side validation is crucial to make sure the user data you manipulate is sound and safe.
**Why you need both layers:**
- **Client-side validation**: Provides immediate feedback and improves user experience
- **Server-side validation**: Ensures security and handles complex business rules
- **Combined approach**: Creates robust, user-friendly, and secure applications
- **Progressive enhancement**: Works even when JavaScript is disabled
> 🛡️ **Security Reminder**: Never trust client-side validation alone! Malicious users can bypass client-side checks, so server-side validation is essential for security and data integrity.
---
@ -292,6 +745,8 @@ Use the Agent mode to complete the following challenge:
**Prompt:** Create a complete form validation system for the registration form that includes: 1) Real-time validation feedback for each field as the user types, 2) Custom validation messages that appear below each input field, 3) A password confirmation field with matching validation, 4) Visual indicators (like green checkmarks for valid fields and red warnings for invalid ones), 5) A submit button that only becomes enabled when all validations pass. Use HTML5 validation attributes, CSS for styling the validation states, and JavaScript for the interactive behavior.
Learn more about [agent mode](https://code.visualstudio.com/blogs/2025/02/24/introducing-copilot-agent-mode) here.
## 🚀 Challenge
Show an error message in the HTML if the user already exists.

@ -1,13 +1,150 @@
# Style your bank app
# Style Your Bank App with Modern CSS
## Project Overview
Transform your functional banking application into a visually appealing, professional-looking web app using modern CSS techniques. You'll create a cohesive design system that enhances user experience while maintaining accessibility and responsive design principles.
This assignment challenges you to apply contemporary web design patterns, implement a consistent visual identity, and create an interface that users will find both attractive and intuitive to navigate.
## Instructions
Create a new `styles.css` file and add a link to it in your current `index.html` file. In the CSS file you just created add some styling to make the *Login* and *Dashboard* page looks nice and tidy. Try to create a color theme to give your app its own branding.
### Step 1: Setup Your Stylesheet
**Create your CSS foundation:**
1. **Create** a new file called `styles.css` in your project root
2. **Link** the stylesheet in your `index.html` file:
```html
<link rel="stylesheet" href="styles.css">
```
3. **Start** with CSS reset and modern defaults:
```css
/* Modern CSS reset and base styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.6;
color: #333;
}
```
### Step 2: Design System Requirements
**Implement these essential design elements:**
#### Color Palette
- **Primary color**: Choose a professional color for buttons and highlights
- **Secondary color**: Complementary color for accents and secondary actions
- **Neutral colors**: Grays for text, borders, and backgrounds
- **Success/Error colors**: Green for success states, red for errors
#### Typography
- **Heading hierarchy**: Clear distinction between H1, H2, and H3 elements
- **Body text**: Readable font size (minimum 16px) and appropriate line height
- **Form labels**: Clear, accessible text styling
#### Layout and Spacing
- **Consistent spacing**: Use a spacing scale (8px, 16px, 24px, 32px)
- **Grid system**: Organized layout for forms and content sections
- **Responsive design**: Mobile-first approach with breakpoints
### Step 3: Component Styling
**Style these specific components:**
#### Forms
- **Input fields**: Professional borders, focus states, and validation styling
- **Buttons**: Hover effects, disabled states, and loading indicators
- **Labels**: Clear positioning and required field indicators
- **Error messages**: Visible error styling and helpful messaging
#### Navigation
- **Header**: Clean, branded navigation area
- **Links**: Clear hover states and active indicators
- **Logo/Title**: Distinctive branding element
#### Content Areas
- **Sections**: Clear visual separation between different areas
- **Cards**: If using card-based layouts, include shadows and borders
- **Backgrounds**: Appropriate use of white space and subtle backgrounds
### Step 4: Enhanced Features (Optional)
**Consider implementing these advanced features:**
- **Dark mode**: Toggle between light and dark themes
- **Animations**: Subtle transitions and micro-interactions
- **Loading states**: Visual feedback during form submissions
- **Responsive images**: Optimized images for different screen sizes
## Design Inspiration
**Modern banking app characteristics:**
- **Clean, minimal design** with plenty of white space
- **Professional color schemes** (blues, greens, or sophisticated neutrals)
- **Clear visual hierarchy** with prominent call-to-action buttons
- **Accessible contrast ratios** meeting WCAG guidelines
- **Mobile-responsive layouts** that work on all devices
## Technical Requirements
### CSS Organization
```css
/* 1. CSS Custom Properties (Variables) */
:root {
--primary-color: #007bff;
--secondary-color: #6c757d;
/* Add more variables */
}
/* 2. Base Styles */
/* Reset, typography, general elements */
/* 3. Layout */
/* Grid, flexbox, positioning */
/* 4. Components */
/* Forms, buttons, cards */
/* 5. Utilities */
/* Helper classes, responsive utilities */
/* 6. Media Queries */
/* Responsive breakpoints */
```
### Accessibility Requirements
- **Color contrast**: Ensure at least 4.5:1 ratio for normal text
- **Focus indicators**: Visible focus states for keyboard navigation
- **Form labels**: Properly associated with inputs
- **Responsive design**: Usable on screens from 320px to 1920px wide
## Evaluation Rubric
| Criteria | Exemplary (A) | Proficient (B) | Developing (C) | Needs Improvement (F) |
|----------|---------------|----------------|----------------|----------------------|
| **Design System** | Implements comprehensive design system with consistent colors, typography, and spacing throughout | Uses consistent styling with clear design patterns and good visual hierarchy | Basic styling applied with some consistency issues or missing design elements | Minimal styling with inconsistent or conflicting design choices |
| **User Experience** | Creates intuitive, professional interface with excellent usability and visual appeal | Provides good user experience with clear navigation and readable content | Basic usability with some UX improvements needed | Poor usability, difficult to navigate or read |
| **Technical Implementation** | Uses modern CSS techniques, organized code structure, and follows best practices | Implements CSS effectively with good organization and appropriate techniques | CSS works correctly but may lack organization or modern approaches | Poor CSS implementation with technical issues or browser compatibility problems |
| **Responsive Design** | Fully responsive design that works beautifully across all device sizes | Good responsive behavior with minor issues on some screen sizes | Basic responsive implementation with some layout issues | Not responsive or significant problems on mobile devices |
| **Accessibility** | Meets WCAG guidelines with excellent keyboard navigation and screen reader support | Good accessibility practices with proper contrast and focus indicators | Basic accessibility considerations with some missing elements | Poor accessibility, difficult for users with disabilities |
## Submission Guidelines
> Tip: you can modify the HTML and add new elements and classes if needed.
**Include in your submission:**
- **styles.css**: Your complete stylesheet
- **Updated HTML**: Any HTML modifications you made
- **Screenshots**: Images showing your design on desktop and mobile
- **README**: Brief description of your design choices and color palette
## Rubric
**Bonus points for:**
- **CSS custom properties** for maintainable theming
- **Advanced CSS features** like Grid, Flexbox, or CSS animations
- **Performance considerations** like optimized CSS and minimal file size
- **Cross-browser testing** ensuring compatibility across different browsers
| Criteria | Exemplary | Adequate | Needs Improvement |
| -------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------- |
| | All pages looks clean and readable, with a consistent color theme and and the different sections standing out properly. | Pages are styled but without a theme or with sections not clearly delimitated. | Pages lack styling, the sections looks disorganized and the information is difficult to read. |
> 💡 **Pro Tip**: Start with mobile design first, then enhance for larger screens. This mobile-first approach ensures your app works well on all devices and follows modern web development best practices.

@ -1,58 +1,141 @@
# Build a Banking App Part 3: Methods of Fetching and Using Data
## Pre-Lecture Quiz
Think about the Enterprise's computer in Star Trek - when Captain Picard asks for ship status, the information appears instantly without the whole interface shutting down and rebuilding itself. That seamless flow of information is exactly what we're building here with dynamic data fetching.
[Pre-lecture quiz](https://ff-quizzes.netlify.app/web/quiz/45)
Right now, your banking app is like a printed newspaper - informative but static. We're going to transform it into something more like mission control at NASA, where data flows continuously and updates in real-time without interrupting the user's workflow.
### Introduction
You'll learn how to communicate with servers asynchronously, handle data that arrives at different times, and transform raw information into something meaningful for your users. This is the difference between a demo and production-ready software.
At the core of every web application there's *data*. Data can take many forms, but its main purpose is always to display information to the user. With web apps becoming increasingly interactive and complex, how the user accesses and interacts with information is now a key part of web development.
## Pre-Lecture Quiz
In this lesson, we'll see how to fetch data from a server asynchronously, and use this data to display information on a web page without reloading the HTML.
[Pre-lecture quiz](https://ff-quizzes.netlify.app/web/quiz/45)
### Prerequisite
### Prerequisites
You need to have built the [Login and Registration Form](../2-forms/README.md) part of the web app for this lesson. You also need to install [Node.js](https://nodejs.org) and [run the server API](../api/README.md) locally so you get account data.
Before diving into data fetching, ensure you have these components ready:
You can test that the server is running properly by executing this command in a terminal:
- **Previous Lesson**: Complete the [Login and Registration Form](../2-forms/README.md) - we'll build on this foundation
- **Local Server**: Install [Node.js](https://nodejs.org) and [run the server API](../api/README.md) to provide account data
- **API Connection**: Test your server connection with this command:
```sh
```bash
curl http://localhost:5000/api
# -> should return "Bank API v1.0.0" as a result
# Expected response: "Bank API v1.0.0"
```
This quick test ensures all components are communicating properly:
- Verifies that Node.js is running correctly on your system
- Confirms your API server is active and responding
- Validates that your app can reach the server (like checking radio contact before a mission)
---
## AJAX and data fetching
## Understanding Data Fetching in Modern Web Apps
The way web applications handle data has evolved dramatically over the past two decades. Understanding this evolution will help you appreciate why modern techniques like AJAX and the Fetch API are so powerful and why they've become essential tools for web developers.
Let's explore how traditional websites worked compared to the dynamic, responsive applications we build today.
Traditional web sites update the content displayed when the user selects a link or submits data using a form, by reloading the full HTML page. Every time new data needs to be loaded, the web server returns a brand new HTML page that needs to be processed by the browser, interrupting the current user action and limiting interactions during the reload. This workflow is also called a *Multi-Page Application* or *MPA*.
### Traditional Multi-Page Applications (MPA)
In the early days of the web, every click was like changing channels on an old television - the screen would go blank, then slowly tune into the new content. This was the reality of early web applications, where every interaction meant completely rebuilding the entire page from scratch.
```mermaid
sequenceDiagram
participant User
participant Browser
participant Server
User->>Browser: Clicks link or submits form
Browser->>Server: Requests new HTML page
Note over Browser: Page goes blank
Server->>Browser: Returns complete HTML page
Browser->>User: Displays new page (flash/reload)
```
![Update workflow in a multi-page application](./images/mpa.png)
When web applications started to become more complex and interactive, a new technique called [AJAX (Asynchronous JavaScript and XML)](https://en.wikipedia.org/wiki/Ajax_(programming)) emerged. This technique allows web apps to send and retrieve data from a server asynchronously using JavaScript, without having to reload the HTML page, resulting in faster updates and smoother user interactions. When new data is received from the server, the current HTML page can also be updated with JavaScript using the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model) API. Over time, this approach has evolved into what is now called a [*Single-Page Application* or *SPA*](https://en.wikipedia.org/wiki/Single-page_application).
**Why this approach felt clunky:**
- Every click meant rebuilding the entire page from scratch
- Users got interrupted mid-thought by those annoying page flashes
- Your internet connection worked overtime downloading the same header and footer repeatedly
- Apps felt more like clicking through a filing cabinet than using software
### Modern Single-Page Applications (SPA)
AJAX (Asynchronous JavaScript and XML) changed this paradigm entirely. Like the modular design of the International Space Station, where astronauts can replace individual components without rebuilding the entire structure, AJAX allows us to update specific parts of a webpage without reloading everything. Despite the name mentioning XML, we mostly use JSON today, but the core principle remains: update only what needs to change.
```mermaid
sequenceDiagram
participant User
participant Browser
participant JavaScript
participant Server
User->>Browser: Interacts with page
Browser->>JavaScript: Triggers event handler
JavaScript->>Server: Fetches only needed data
Server->>JavaScript: Returns JSON data
JavaScript->>Browser: Updates specific page elements
Browser->>User: Shows updated content (no reload)
```
![Update workflow in a single-page application](./images/spa.png)
When AJAX was first introduced, the only API available to fetch data asynchronously was [`XMLHttpRequest`](https://developer.mozilla.org/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest). But modern browsers now also implement the more convenient and powerful [`Fetch` API](https://developer.mozilla.org/docs/Web/API/Fetch_API), which uses promises and is better suited to manipulate JSON data.
**Why SPAs feel so much better:**
- Only the parts that actually changed get updated (smart, right?)
- No more jarring interruptions - your users stay in their flow
- Less data traveling over the wire means faster loading
- Everything feels snappy and responsive, like the apps on your phone
### The Evolution to Modern Fetch API
Modern browsers provide the [`Fetch` API](https://developer.mozilla.org/docs/Web/API/Fetch_API), which replaces the older [`XMLHttpRequest`](https://developer.mozilla.org/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest). Like the difference between operating a telegraph and using email, Fetch API uses promises for cleaner asynchronous code and handles JSON naturally.
> While all modern browsers support the `Fetch API`, if you want your web application to work on legacy or old browsers it's always a good idea to check the [compatibility table on caniuse.com](https://caniuse.com/fetch) first.
| Feature | XMLHttpRequest | Fetch API |
|---------|----------------|----------|
| **Syntax** | Complex callback-based | Clean promise-based |
| **JSON Handling** | Manual parsing required | Built-in `.json()` method |
| **Error Handling** | Limited error information | Comprehensive error details |
| **Modern Support** | Legacy compatibility | ES6+ promises and async/await |
### Task
> 💡 **Browser Compatibility**: Good news - the Fetch API works in all modern browsers! If you're curious about specific versions, [caniuse.com](https://caniuse.com/fetch) has the complete compatibility story.
>
**The bottom line:**
- Works great in Chrome, Firefox, Safari, and Edge (basically everywhere your users are)
- Only Internet Explorer needs extra help (and honestly, it's time to let IE go)
- Sets you up perfectly for the elegant async/await patterns we'll use later
In [the previous lesson](../2-forms/README.md) we implemented the registration form to create an account. We'll now add code to login using an existing account, and fetch its data. Open the `app.js` file and add a new `login` function:
### Implementing User Login and Data Retrieval
```js
Now let's implement the login system that transforms your banking app from a static display into a functional application. Like the authentication protocols used in secure military facilities, we'll verify user credentials and then provide access to their specific data.
We'll build this incrementally, starting with basic authentication and then adding the data-fetching capabilities.
#### Step 1: Create the Login Function Foundation
Open your `app.js` file and add a new `login` function. This will handle the user authentication process:
```javascript
async function login() {
const loginForm = document.getElementById('loginForm')
const loginForm = document.getElementById('loginForm');
const user = loginForm.user.value;
}
```
Here we start by retrieving the form element with `getElementById()`, and then we get the username from the input with `loginForm.user.value`. Every form control can be accessed by its name (set in the HTML using the `name` attribute) as a property of the form.
**Let's break this down:**
- That `async` keyword? It's telling JavaScript "hey, this function might need to wait for things"
- We're grabbing our form from the page (nothing fancy, just finding it by its ID)
- Then we're pulling out whatever the user typed as their username
- Here's a neat trick: you can access any form input by its `name` attribute - no need for extra getElementById calls!
> 💡 **Form Access Pattern**: Every form control can be accessed by its name (set in the HTML using the `name` attribute) as a property of the form element. This provides a clean, readable way to get form data.
#### Step 2: Create the Account Data Fetching Function
In a similar fashion to what we did for the registration, we'll create another function to perform a server request, but this time for retrieving the account data:
Next, we'll create a dedicated function to retrieve account data from the server. This follows the same pattern as your registration function but focuses on data retrieval:
```js
```javascript
async function getAccount(user) {
try {
const response = await fetch('//localhost:5000/api/accounts/' + encodeURIComponent(user));
@ -63,15 +146,41 @@ async function getAccount(user) {
}
```
We use the `fetch` API to request the data asynchronously from the server, but this time we don't need any extra parameters other than the URL to call, as we're only querying data. By default, `fetch` creates a [`GET`](https://developer.mozilla.org/docs/Web/HTTP/Methods/GET) HTTP request, which is what we are seeking here.
**Here's what this code accomplishes:**
- **Uses** the modern `fetch` API to request data asynchronously
- **Constructs** a GET request URL with the username parameter
- **Applies** `encodeURIComponent()` to safely handle special characters in URLs
- **Converts** the response to JSON format for easy data manipulation
- **Handles** errors gracefully by returning an error object instead of crashing
`encodeURIComponent()` is a function that escapes special characters for URL. What issues could we possibly have if we do not call this function and use directly the `user` value in the URL?
> ⚠️ **Security Note**: The `encodeURIComponent()` function handles special characters in URLs. Like the encoding systems used in naval communications, it ensures your message arrives exactly as intended, preventing characters like "#" or "&" from being misinterpreted.
>
**Why this matters:**
- Prevents special characters from breaking URLs
- Protects against URL manipulation attacks
- Ensures your server receives the intended data
- Follows secure coding practices
Let's now update our `login` function to use `getAccount`:
#### Understanding HTTP GET Requests
```js
Here's something that might surprise you: when you use `fetch` without any extra options, it automatically creates a [`GET`](https://developer.mozilla.org/docs/Web/HTTP/Methods/GET) request. This is perfect for what we're doing - asking the server "hey, can I see this user's account data?"
Think of GET requests like politely asking to borrow a book from the library - you're requesting to see something that already exists. POST requests (which we used for registration) are more like submitting a new book to be added to the collection.
| GET Request | POST Request |
|-------------|-------------|
| **Purpose** | Retrieve existing data | Send new data to server |
| **Parameters** | In URL path/query string | In request body |
| **Caching** | Can be cached by browsers | Not typically cached |
| **Security** | Visible in URL/logs | Hidden in request body |
#### Step 3: Bringing It All Together
Now for the satisfying part - let's connect your account fetching function to the login process. This is where everything clicks into place:
```javascript
async function login() {
const loginForm = document.getElementById('loginForm')
const loginForm = document.getElementById('loginForm');
const user = loginForm.user.value;
const data = await getAccount(user);
@ -84,94 +193,261 @@ async function login() {
}
```
First, as `getAccount` is an asynchronous function we need to match it with the `await` keyword to wait for the server result. As with any server request, we also have to deal with error cases. For now we'll only add a log message to display the error, and come back to it later.
This function follows a clear sequence:
- Extract the username from the form input
- Request the user's account data from the server
- Handle any errors that occur during the process
- Store the account data and navigate to the dashboard upon success
> 🎯 **Async/Await Pattern**: Since `getAccount` is an asynchronous function, we use the `await` keyword to pause execution until the server responds. This prevents the code from continuing with undefined data.
Then we have to store the data somewhere so we can later use it to display the dashboard information. Since the `account` variable does not exist yet, we'll create a global variable for it at the top of our file:
#### Step 4: Creating a Home for Your Data
```js
Your app needs somewhere to remember the account information once it's loaded. Think of this like your app's short-term memory - a place to keep the current user's data handy. Add this line at the top of your `app.js` file:
```javascript
// This holds the current user's account data
let account = null;
```
After the user data is saved into a variable we can navigate from the *login* page to the *dashboard* using the `navigate()` function we already have.
**Why we need this:**
- Keeps the account data accessible from anywhere in your app
- Starting with `null` means "no one's logged in yet"
- Gets updated when someone successfully logs in or registers
- Acts like a single source of truth - no confusion about who's logged in
#### Step 5: Wire Up Your Form
Finally, we need to call our `login` function when the login form is submitted, by modifying the HTML:
Now let's connect your shiny new login function to your HTML form. Update your form tag like this:
```html
<form id="loginForm" action="javascript:login()">
<!-- Your existing form inputs -->
</form>
```
Test that everything is working correctly by registering a new account and trying to login using the same account.
**What this little change does:**
- Stops the form from doing its default "reload the whole page" behavior
- Calls your custom JavaScript function instead
- Keeps everything smooth and single-page-app-like
- Gives you complete control over what happens when users hit "Login"
#### Step 6: Enhance Your Registration Function
Before moving on to the next part, we can also complete the `register` function by adding this at the bottom of the function:
For consistency, update your `register` function to also store account data and navigate to the dashboard:
```js
```javascript
// Add these lines at the end of your register function
account = result;
navigate('/dashboard');
```
✅ Did you know that by default, you can only call server APIs from the *same domain and port* than the web page you are viewing? This is security mechanism enforced by browsers. But wait, our web app is running on `localhost:3000` whereas the server API is running on ` localhost:5000`, why does it work? By using a technique called [Cross-Origin Resource Sharing (CORS)](https://developer.mozilla.org/docs/Web/HTTP/CORS), it is possible to perform cross-origin HTTP requests if the server adds special headers to the response, allowing exceptions for specific domains.
**This enhancement provides:**
- **Seamless** transition from registration to dashboard
- **Consistent** user experience between login and registration flows
- **Immediate** access to account data after successful registration
#### Testing Your Implementation
```mermaid
flowchart TD
A[User enters credentials] --> B[Login function called]
B --> C[Fetch account data from server]
C --> D{Data received successfully?}
D -->|Yes| E[Store account data globally]
D -->|No| F[Display error message]
E --> G[Navigate to dashboard]
F --> H[User stays on login page]
```
**Time to take it for a spin:**
1. Create a new account to make sure everything's working
2. Try logging in with those same credentials
3. Peek at your browser's console (F12) if anything seems off
4. Make sure you land on the dashboard after a successful login
If something's not working, don't panic! Most issues are simple fixes like typos or forgetting to start the API server.
#### A Quick Word About Cross-Origin Magic
You might be wondering: "How is my web app talking to this API server when they're running on different ports?" Great question! This touches on something every web developer bumps into eventually.
> 🔒 **Cross-Origin Security**: Browsers enforce a "same-origin policy" to prevent unauthorized communication between different domains. Like the checkpoint system at the Pentagon, they verify that communication is authorized before allowing data transfer.
>
**In our setup:**
- Your web app runs on `localhost:3000` (development server)
- Your API server runs on `localhost:5000` (backend server)
- The API server includes [CORS headers](https://developer.mozilla.org/docs/Web/HTTP/CORS) that explicitly authorize communication from your web app
This configuration mirrors real-world development where frontend and backend applications typically run on separate servers.
> 📚 **Learn More**: Dive deeper into APIs and data fetching with this comprehensive [Microsoft Learn module on APIs](https://docs.microsoft.com/learn/modules/use-apis-discover-museum-art/?WT.mc_id=academic-77807-sagibbon).
## Bringing Your Data to Life in HTML
Now we'll make the fetched data visible to users through DOM manipulation. Like the process of developing photographs in a darkroom, we're taking invisible data and rendering it into something users can see and interact with.
DOM manipulation is the technique that transforms static web pages into dynamic applications that update their content based on user interactions and server responses.
### Choosing the Right Tool for the Job
When it comes to updating your HTML with JavaScript, you've got several options. Think of these like different tools in a toolbox - each one perfect for specific jobs:
| Method | What it's great for | When to use it | Safety level |
|--------|---------------------|----------------|--------------|
| `textContent` | Displaying user data safely | Any time you're showing text | ✅ Rock solid |
| `createElement()` + `append()` | Building complex layouts | Creating new sections/lists | ✅ Bulletproof |
| `innerHTML` | Setting HTML content | ⚠️ Try to avoid this one | ❌ Risky business |
#### The Safe Way to Show Text: textContent
The [`textContent`](https://developer.mozilla.org/docs/Web/API/Node/textContent) property is your best friend when displaying user data. It's like having a bouncer for your webpage - nothing harmful gets through:
```javascript
// The safe, reliable way to update text
const balanceElement = document.getElementById('balance');
balanceElement.textContent = account.balance;
```
**Benefits of textContent:**
- Treats everything as plain text (prevents script execution)
- Automatically clears existing content
- Efficient for simple text updates
- Provides built-in security against malicious content
#### Creating Dynamic HTML Elements
> Learn more about APIs by taking this [lesson](https://docs.microsoft.com/learn/modules/use-apis-discover-museum-art/?WT.mc_id=academic-77807-sagibbon)
For more complex content, combine [`document.createElement()`](https://developer.mozilla.org/docs/Web/API/Document/createElement) with the [`append()`](https://developer.mozilla.org/docs/Web/API/ParentNode/append) method:
## Update HTML to display data
```javascript
// Safe way to create new elements
const transactionItem = document.createElement('div');
transactionItem.className = 'transaction-item';
transactionItem.textContent = `${transaction.date}: ${transaction.description}`;
container.append(transactionItem);
```
Now that we have the user data, we have to update the existing HTML to display it. We already know how to retrieve an element from the DOM using for example `document.getElementById()`. After you have a base element, here are some APIs you can use to modify it or add child elements to it:
**Understanding this approach:**
- **Creates** new DOM elements programmatically
- **Maintains** full control over element attributes and content
- **Allows** for complex, nested element structures
- **Preserves** security by separating structure from content
- Using the [`textContent`](https://developer.mozilla.org/docs/Web/API/Node/textContent) property you can change the text of an element. Note that changing this value removes all the element's children (if there's any) and replaces it with the text provided. As such, it's also an efficient method to remove all children of a given element by assigning an empty string `''` to it.
> ⚠️ **Security Consideration**: While [`innerHTML`](https://developer.mozilla.org/docs/Web/API/Element/innerHTML) appears in many tutorials, it can execute embedded scripts. Like the security protocols at CERN that prevent unauthorized code execution, using `textContent` and `createElement` provides safer alternatives.
>
**Risks of innerHTML:**
- Executes any `<script>` tags in user data
- Vulnerable to code injection attacks
- Creates potential security vulnerabilities
- The safer alternatives we're using provide equivalent functionality
- Using [`document.createElement()`](https://developer.mozilla.org/docs/Web/API/Document/createElement) along with the [`append()`](https://developer.mozilla.org/docs/Web/API/ParentNode/append) method you can create and attach one or more new child elements.
### Making Errors User-Friendly
✅ Using the [`innerHTML`](https://developer.mozilla.org/docs/Web/API/Element/innerHTML) property of an element it's also possible to change its HTML contents, but this one should be avoided as it's vulnerable to [cross-site scripting (XSS)](https://developer.mozilla.org/docs/Glossary/Cross-site_scripting) attacks.
Currently, login errors only appear in the browser console, which is invisible to users. Like the difference between a pilot's internal diagnostics and the passenger information system, we need to communicate important information through the appropriate channel.
### Task
Implementing visible error messages provides users with immediate feedback about what went wrong and how to proceed.
Before moving on to the dashboard screen, there's one more thing we should do on the *login* page. Currently, if you try to login with a username that does not exist, a message is shown in the console but for a normal user nothing changes and you don't know what's going on.
#### Step 1: Add a Spot for Error Messages
Let's add a placeholder element in the login form where we can display an error message if needed. A good place would be just before the login `<button>`:
First, let's give error messages a home in your HTML. Add this right before your login button so users will see it naturally:
```html
...
<div id="loginError"></div>
<!-- This is where error messages will appear -->
<div id="loginError" role="alert"></div>
<button>Login</button>
...
```
This `<div>` element is empty, meaning that nothing will be displayed on the screen until we add some content to it. We also give it an `id` so we can retrieve it easily with JavaScript.
**What's happening here:**
- We're creating an empty container that stays invisible until needed
- It's positioned where users naturally look after clicking "Login"
- That `role="alert"` is a nice touch for screen readers - it tells assistive technology "hey, this is important!"
- The unique `id` gives our JavaScript an easy target
#### Step 2: Create a Handy Helper Function
Go back to the `app.js` file and create a new helper function `updateElement`:
Let's make a little utility function that can update any element's text. This is one of those "write once, use everywhere" functions that'll save you time:
```js
```javascript
function updateElement(id, text) {
const element = document.getElementById(id);
element.textContent = text;
}
```
This one is quite straightforward: given an element *id* and *text*, it will update the text content of the DOM element with the matching `id`. Let's use this method in place of the previous error message in the `login` function:
**Function benefits:**
- Simple interface requiring only an element ID and text content
- Safely locates and updates DOM elements
- Reusable pattern that reduces code duplication
- Maintains consistent updating behavior across the application
#### Step 3: Show Errors Where Users Can See Them
Now let's replace that hidden console message with something users can actually see. Update your login function:
```js
```javascript
// Instead of just logging to console, show the user what's wrong
if (data.error) {
return updateElement('loginError', data.error);
}
```
Now if you try to login with an invalid account, you should see something like this:
**This small change makes a big difference:**
- Error messages appear right where users are looking
- No more mysterious silent failures
- Users get immediate, actionable feedback
- Your app starts feeling professional and thoughtful
Now when you test with an invalid account, you'll see a helpful error message right on the page!
![Screenshot showing the error message displayed during login](./images/login-error.png)
Now we have error text that shows up visually, but if you try it with a screen reader you'll notice that nothing is announced. In order for text that is dynamically added to a page to be announced by screen readers, it will need to use something called a [Live Region](https://developer.mozilla.org/docs/Web/Accessibility/ARIA/ARIA_Live_Regions). Here we're going to use a specific type of live region called an alert:
#### Step 4: Being Inclusive with Accessibility
Here's something cool about that `role="alert"` we added earlier - it's not just decoration! This little attribute creates what's called a [Live Region](https://developer.mozilla.org/docs/Web/Accessibility/ARIA/ARIA_Live_Regions) that immediately announces changes to screen readers:
```html
<div id="loginError" role="alert"></div>
```
Implement the same behavior for the `register` function errors (don't forget to update the HTML).
**Why this matters:**
- Screen reader users hear the error message as soon as it appears
- Everyone gets the same important information, regardless of how they navigate
- It's a simple way to make your app work for more people
- Shows you care about creating inclusive experiences
Small touches like this separate good developers from great ones!
## Display information on the dashboard
#### Step 5: Apply the Same Pattern to Registration
Using the same techniques we've just seen, we'll also take care of displaying the account information on the dashboard page.
For consistency, implement identical error handling in your registration form:
This is what an account object received from the server looks like:
1. **Add** an error display element to your registration HTML:
```html
<div id="registerError" role="alert"></div>
```
2. **Update** your register function to use the same error display pattern:
```javascript
if (data.error) {
return updateElement('registerError', data.error);
}
```
**Benefits of consistent error handling:**
- **Provides** uniform user experience across all forms
- **Reduces** cognitive load by using familiar patterns
- **Simplifies** maintenance with reusable code
- **Ensures** accessibility standards are met throughout the app
## Creating Your Dynamic Dashboard
Now we'll transform your static dashboard into a dynamic interface that displays real account data. Like the difference between a printed flight schedule and the live departure boards at airports, we're moving from static information to real-time, responsive displays.
Using the DOM manipulation techniques you've learned, we'll create a dashboard that updates automatically with current account information.
### Getting to Know Your Data
Before we start building, let's peek at what kind of data your server sends back. When someone successfully logs in, here's the treasure trove of information you get to work with:
```json
{
@ -183,15 +459,34 @@ This is what an account object received from the server looks like:
{ "id": "1", "date": "2020-10-01", "object": "Pocket money", "amount": 50 },
{ "id": "2", "date": "2020-10-03", "object": "Book", "amount": -10 },
{ "id": "3", "date": "2020-10-04", "object": "Sandwich", "amount": -5 }
],
]
}
```
> Note: to make your life easier, you can use the pre-existing `test` account that's already populated with data.
**This data structure provides:**
- **`user`**: Perfect for personalizing the experience ("Welcome back, Sarah!")
- **`currency`**: Makes sure we display money amounts correctly
- **`description`**: A friendly name for the account
- **`balance`**: The all-important current balance
- **`transactions`**: The complete transaction history with all the details
### Task
Everything you need to build a professional-looking banking dashboard!
Let's start by replacing the "Balance" section in the HTML to add placeholder elements:
> 💡 **Pro Tip**: Want to see your dashboard in action right away? Use the username `test` when you log in - it comes pre-loaded with sample data so you can see everything working without having to create transactions first.
>
**Why the test account is handy:**
- Comes with realistic sample data already loaded
- Perfect for seeing how transactions display
- Great for testing your dashboard features
- Saves you from having to create dummy data manually
### Creating the Dashboard Display Elements
Let's build your dashboard interface step by step, starting with the account summary information and then moving on to more complex features like transaction lists.
#### Step 1: Update Your HTML Structure
First, replace the static "Balance" section with dynamic placeholder elements that your JavaScript can populate:
```html
<section>
@ -199,17 +494,25 @@ Let's start by replacing the "Balance" section in the HTML to add placeholder el
</section>
```
We'll also add a new section just below to display the account description:
Next, add a section for the account description. Since this acts as a title for the dashboard content, use semantic HTML:
```html
<h2 id="description"></h2>
```
✅ Since the account description functions as a title for the content underneath it, it is marked up semantically as a heading. Learn more about how [heading structure](https://www.nomensa.com/blog/2017/how-structure-headings-web-accessibility) is important for accessibility, and take a critical look at the page to determine what else could be a heading.
**Understanding the HTML structure:**
- **Uses** separate `<span>` elements for balance and currency for individual control
- **Applies** unique IDs to each element for JavaScript targeting
- **Follows** semantic HTML by using `<h2>` for the account description
- **Creates** a logical hierarchy for screen readers and SEO
> ✅ **Accessibility Insight**: The account description functions as a title for the dashboard content, so it's marked up semantically as a heading. Learn more about how [heading structure](https://www.nomensa.com/blog/2017/how-structure-headings-web-accessibility) impacts accessibility. Can you identify other elements on your page that might benefit from heading tags?
#### Step 2: Create the Dashboard Update Function
Next, we'll create a new function in `app.js` to fill in the placeholder:
Now create a function that populates your dashboard with real account data:
```js
```javascript
function updateDashboard() {
if (!account) {
return navigate('/login');
@ -221,40 +524,71 @@ function updateDashboard() {
}
```
First, we check that we have the account data we need before going further. Then we use the `updateElement()` function we created earlier to update the HTML.
**Step by step, here's what this function does:**
- **Validates** that account data exists before proceeding
- **Redirects** unauthenticated users back to the login page
- **Updates** the account description using the reusable `updateElement` function
- **Formats** the balance to always show two decimal places
- **Displays** the appropriate currency symbol
> To make the balance display prettier, we use the method [`toFixed(2)`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed) to force displaying the value with 2 digits after the decimal point.
> 💰 **Money Formatting**: That [`toFixed(2)`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed) method is a lifesaver! It ensures your balance always looks like real money - "75.00" instead of just "75". Your users will appreciate seeing familiar currency formatting.
Now we need to call our `updateDashboard()` function every time the dashboard is loaded. If you already finished the [lesson 1 assignment](../1-template-route/assignment.md) this should be straightforward, otherwise you can use the following implementation.
#### Step 3: Making Sure Your Dashboard Updates
Add this code to the end of the `updateRoute()` function:
To ensure your dashboard refreshes with current data every time someone visits it, we need to hook into your navigation system. If you completed the [lesson 1 assignment](../1-template-route/assignment.md), this should feel familiar. If not, don't worry - here's what you need:
```js
Add this to the end of your `updateRoute()` function:
```javascript
if (typeof route.init === 'function') {
route.init();
}
```
And update the routes definition with:
Then update your routes to include the dashboard initialization:
```js
```javascript
const routes = {
'/login': { templateId: 'login' },
'/dashboard': { templateId: 'dashboard', init: updateDashboard }
};
```
With this change, every time the dashboard page is displayed, the function `updateDashboard()` is called. After a login, you should then be able to see the account balance, currency and description.
**What this clever setup does:**
- Checks if a route has special initialization code
- Runs that code automatically when the route loads
- Ensures your dashboard always shows fresh, current data
- Keeps your routing logic clean and organized
#### Testing Your Dashboard
After implementing these changes, test your dashboard:
1. **Log in** with a test account
2. **Verify** you're redirected to the dashboard
3. **Check** that the account description, balance, and currency display correctly
4. **Try logging out and back in** to ensure data refreshes properly
Your dashboard should now display dynamic account information that updates based on the logged-in user's data!
## Building Smart Transaction Lists with Templates
## Create table rows dynamically with HTML templates
Instead of manually creating HTML for each transaction, we'll use templates to generate consistent formatting automatically. Like the standardized components used in spacecraft manufacturing, templates ensure every transaction row follows the same structure and appearance.
In the [first lesson](../1-template-route/README.md) we used HTML templates along with the [`appendChild()`](https://developer.mozilla.org/docs/Web/API/Node/appendChild) method to implement the navigation in our app. Templates can also be smaller and used to dynamically populate repetitive parts of a page.
This technique scales efficiently from a few transactions to thousands, maintaining consistent performance and presentation.
We'll use a similar approach to display the list of transactions in the HTML table.
```mermaid
flowchart LR
A[Transaction Data] --> B[HTML Template]
B --> C[Clone Template]
C --> D[Populate with Data]
D --> E[Add to DOM]
E --> F[Repeat for Each Transaction]
```
### Task
### Step 1: Create the Transaction Template
Add a new template in the HTML `<body>`:
First, add a reusable template for transaction rows in your HTML `<body>`:
```html
<template id="transaction">
@ -266,17 +600,30 @@ Add a new template in the HTML `<body>`:
</template>
```
This template represents a single table row, with the 3 columns we want to populate: *date*, *object* and *amount* of a transaction.
**Understanding HTML templates:**
- **Defines** the structure for a single table row
- **Remains** invisible until cloned and populated with JavaScript
- **Includes** three cells for date, description, and amount
- **Provides** a reusable pattern for consistent formatting
### Step 2: Prepare Your Table for Dynamic Content
Then, add this `id` property to the `<tbody>` element of the table within the dashboard template to make it easier to find using JavaScript:
Next, add an `id` to your table body so JavaScript can easily target it:
```html
<tbody id="transactions"></tbody>
```
Our HTML is ready, let's switch to JavaScript code and create a new function `createTransactionRow`:
**What this accomplishes:**
- **Creates** a clear target for inserting transaction rows
- **Separates** the table structure from the dynamic content
- **Enables** easy clearing and repopulating of transaction data
### Step 3: Build the Transaction Row Factory Function
```js
Now create a function that transforms transaction data into HTML elements:
```javascript
function createTransactionRow(transaction) {
const template = document.getElementById('transaction');
const transactionRow = template.content.cloneNode(true);
@ -288,9 +635,19 @@ function createTransactionRow(transaction) {
}
```
This function does exactly what its names implies: using the template we created earlier, it creates a new table row and fills in its contents using transaction data. We'll use this in our `updateDashboard()` function to populate the table:
**Breaking down this factory function:**
- **Retrieves** the template element by its ID
- **Clones** the template content for safe manipulation
- **Selects** the table row within the cloned content
- **Populates** each cell with transaction data
- **Formats** the amount to show proper decimal places
- **Returns** the completed row ready for insertion
### Step 4: Generate Multiple Transaction Rows Efficiently
Add this code to your `updateDashboard()` function to display all transactions:
```js
```javascript
const transactionsRows = document.createDocumentFragment();
for (const transaction of account.transactions) {
const transactionRow = createTransactionRow(transaction);
@ -299,11 +656,20 @@ for (const transaction of account.transactions) {
updateElement('transactions', transactionsRows);
```
Here we use the method [`document.createDocumentFragment()`](https://developer.mozilla.org/docs/Web/API/Document/createDocumentFragment) that creates a new DOM fragment on which we can work, before finally attaching it to our HTML table.
**Understanding this efficient approach:**
- **Creates** a document fragment to batch DOM operations
- **Iterates** through all transactions in the account data
- **Generates** a row for each transaction using the factory function
- **Collects** all rows in the fragment before adding to the DOM
- **Performs** a single DOM update instead of multiple individual insertions
There's still one more thing we have to do before this code can work, as our `updateElement()` function currently supports text content only. Let's change its code a bit:
> ⚡ **Performance Optimization**: [`document.createDocumentFragment()`](https://developer.mozilla.org/docs/Web/API/Document/createDocumentFragment) works like the assembly process at Boeing - components are prepared off the main line, then installed as a complete unit. This batching approach minimizes DOM reflows by performing a single insertion instead of multiple individual operations.
```js
### Step 5: Enhance the Update Function for Mixed Content
Your `updateElement()` function currently only handles text content. Update it to work with both text and DOM nodes:
```javascript
function updateElement(id, textOrNode) {
const element = document.getElementById(id);
element.textContent = ''; // Removes all children
@ -311,9 +677,30 @@ function updateElement(id, textOrNode) {
}
```
We use the [`append()`](https://developer.mozilla.org/docs/Web/API/ParentNode/append) method as it allows to attach either text or [DOM Nodes](https://developer.mozilla.org/docs/Web/API/Node) to a parent element, which is perfect for all our use cases.
**Key improvements in this update:**
- **Clears** existing content before adding new content
- **Accepts** either text strings or DOM nodes as parameters
- **Uses** the [`append()`](https://developer.mozilla.org/docs/Web/API/ParentNode/append) method for flexibility
- **Maintains** backward compatibility with existing text-based usage
### Taking Your Dashboard for a Test Drive
Time for the moment of truth! Let's see your dynamic dashboard in action:
1. Log in using the `test` account (it has sample data ready to go)
2. Navigate to your dashboard
3. Check that transaction rows appear with proper formatting
4. Make sure dates, descriptions, and amounts all look good
If everything's working, you should see a fully functional transaction list on your dashboard! 🎉
If you try using the `test` account to login, you should now see a transaction list on the dashboard 🎉.
**What you've accomplished:**
- Built a dashboard that scales with any amount of data
- Created reusable templates for consistent formatting
- Implemented efficient DOM manipulation techniques
- Developed functionality comparable to production banking applications
You've successfully transformed a static webpage into a dynamic web application.
---
@ -325,14 +712,24 @@ Use the Agent mode to complete the following challenge:
**Prompt:** Create a search functionality for the banking app that includes: 1) A search form with input fields for date range (from/to), minimum/maximum amount, and transaction description keywords, 2) A `filterTransactions()` function that filters the account.transactions array based on the search criteria, 3) Update the `updateDashboard()` function to show filtered results, and 4) Add a "Clear Filters" button to reset the view. Use modern JavaScript array methods like `filter()` and handle edge cases for empty search criteria.
Learn more about [agent mode](https://code.visualstudio.com/blogs/2025/02/24/introducing-copilot-agent-mode) here.
## 🚀 Challenge
Work together to make the dashboard page look like a real banking app. If you already styled your app, try to use [media queries](https://developer.mozilla.org/docs/Web/CSS/Media_Queries) to create a [responsive design](https://developer.mozilla.org/docs/Web/Progressive_web_apps/Responsive/responsive_design_building_blocks) working nicely on both desktop and mobile devices.
Ready to take your banking app to the next level? Let's make it look and feel like something you'd actually want to use. Here are some ideas to spark your creativity:
**Make it beautiful**: Add CSS styling to transform your functional dashboard into something visually appealing. Think clean lines, good spacing, and maybe even some subtle animations.
Here's an example of a styled dashboard page:
**Make it responsive**: Try using [media queries](https://developer.mozilla.org/docs/Web/CSS/Media_Queries) to create a [responsive design](https://developer.mozilla.org/docs/Web/Progressive_web_apps/Responsive/responsive_design_building_blocks) that works great on phones, tablets, and desktops. Your users will thank you!
**Add some flair**: Consider color-coding transactions (green for income, red for expenses), adding icons, or creating hover effects that make the interface feel interactive.
Here's what a polished dashboard could look like:
![Screenshot of an example result of the dashboard after styling](../images/screen2.png)
Don't feel like you have to match this exactly - use it as inspiration and make it your own!
## Post-Lecture Quiz
[Post-lecture quiz](https://ff-quizzes.netlify.app/web/quiz/46)

@ -1,15 +1,128 @@
# Refactor and comment your code
# Code Refactoring and Documentation Assignment
## Learning Objectives
By completing this assignment, you will practice essential software development skills that professional developers use daily. You'll learn to organize code for maintainability, reduce duplication through abstraction, and document your work for future developers (including yourself!).
Clean, well-documented code is crucial for real-world web development projects where multiple developers collaborate and codebases evolve over time.
## Assignment Overview
Your banking app's `app.js` file has grown significantly with login, registration, and dashboard functionality. It's time to refactor this code using professional development practices to improve readability, maintainability, and reduce duplication.
## Instructions
As your codebase grows, it's important to refactor your code frequently to keep it readable and maintainable over time. Add comments and refactor your `app.js` to improve the code quality:
Transform your current `app.js` code by implementing these three core refactoring techniques:
### 1. Extract Configuration Constants
**Task**: Create a configuration section at the top of your file with reusable constants.
**Implementation guidance:**
- Extract the server API base URL (currently hardcoded in multiple places)
- Create constants for error messages that appear in multiple functions
- Consider extracting route paths and element IDs that are used repeatedly
**Example structure:**
```javascript
// Configuration constants
const API_BASE_URL = 'http://localhost:5000/api';
const ROUTES = {
LOGIN: '/login',
DASHBOARD: '/dashboard'
};
```
### 2. Create a Unified Request Function
**Task**: Build a reusable `sendRequest()` function that eliminates duplicate code between `createAccount()` and `getAccount()`.
**Requirements:**
- Handle both GET and POST requests
- Include proper error handling
- Support different URL endpoints
- Accept optional request body data
**Function signature guidance:**
```javascript
async function sendRequest(endpoint, method = 'GET', data = null) {
// Your implementation here
}
```
### 3. Add Professional Code Documentation
**Task**: Document your code with clear, helpful comments that explain the "why" behind your logic.
**Documentation standards:**
- Add function documentation explaining purpose, parameters, and return values
- Include inline comments for complex logic or business rules
- Group related functions together with section headers
- Explain any non-obvious code patterns or browser-specific workarounds
**Example documentation style:**
```javascript
/**
* Authenticates user and redirects to dashboard
* @param {Event} event - Form submission event
* @returns {Promise<void>} - Resolves when login process completes
*/
async function login(event) {
// Prevent default form submission to handle with JavaScript
event.preventDefault();
// Your implementation...
}
```
## Success Criteria
Your refactored code should demonstrate these professional development practices:
### Exemplary Implementation
- ✅ **Constants**: All magic strings and URLs are extracted into clearly named constants
- ✅ **DRY Principle**: Common request logic is consolidated into a reusable `sendRequest()` function
- ✅ **Documentation**: Functions have clear JSDoc comments explaining purpose and parameters
- ✅ **Organization**: Code is logically grouped with section headers and consistent formatting
- ✅ **Error Handling**: Improved error handling using the new request function
### Adequate Implementation
- ✅ **Constants**: Most repeated values are extracted, with minor hardcoded values remaining
- ✅ **Factorization**: Basic `sendRequest()` function created but may not handle all edge cases
- ✅ **Comments**: Key functions are documented, though some explanations could be more complete
- ✅ **Readability**: Code is generally well-organized with some areas for improvement
### Needs Improvement
- ❌ **Constants**: Many magic strings and URLs remain hardcoded throughout the file
- ❌ **Duplication**: Significant code duplication remains between similar functions
- ❌ **Documentation**: Missing or inadequate comments that don't explain the code's purpose
- ❌ **Organization**: Code lacks clear structure and logical grouping
## Testing Your Refactored Code
After refactoring, ensure your banking app still functions correctly:
1. **Test all user flows**: Registration, login, dashboard display, and error handling
2. **Verify API calls**: Confirm that your `sendRequest()` function works for both account creation and retrieval
3. **Check error scenarios**: Test with invalid credentials and network errors
4. **Review console output**: Ensure no new errors were introduced during refactoring
## Submission Guidelines
Submit your refactored `app.js` file with:
- Clear section headers organizing different functionality
- Consistent code formatting and indentation
- Complete JSDoc documentation for all functions
- A brief comment at the top explaining your refactoring approach
**Bonus Challenge**: Create a simple code documentation file (`CODE_STRUCTURE.md`) that explains your app's architecture and how the different functions work together.
- Extract constants, like the server API base URL
- Factorize similar code: for example you can create a `sendRequest()` function to regroup the code used in both `createAccount()` and `getAccount()`
- Reorganize the code to make it easier to read, and add comments
## Real-World Connection
## Rubric
This assignment mirrors the type of code maintenance that professional developers perform regularly. In industry settings:
- **Code reviews** evaluate readability and maintainability like this assignment
- **Technical debt** accumulates when code isn't regularly refactored and documented
- **Team collaboration** depends on clear, well-documented code that new team members can understand
- **Bug fixes** are much easier in well-organized codebases with proper abstractions
| Criteria | Exemplary | Adequate | Needs Improvement |
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| | Code is commented, well-organized in different sections and easy to read. Constants are extracted and a factorized `sendRequest()` function has been created. | Code is clean but can still be improved with more comments, constant extraction or factorization. | Code is messy, not commented, constants are not extracted and code is not factorized. |
The skills you're practicing here - extracting constants, eliminating duplication, and writing clear documentation - are fundamental to professional software development.

@ -4,61 +4,106 @@
[Pre-lecture quiz](https://ff-quizzes.netlify.app/web/quiz/47)
### Introduction
## Introduction
As a web application grows, it becomes a challenge to keep track of all data flows. Which code gets the data, what page consumes it, where and when does it need to be updated...it's easy to end up with messy code that's difficult to maintain. This is especially true when you need to share data among different pages of your app, for example user data. The concept of *state management* has always existed in all kinds of programs, but as web apps keep growing in complexity it's now a key point to think about during development.
State management is like the navigation system on the Voyager spacecraft when everything's working smoothly, you barely notice it's there. But when things go wrong, it becomes the difference between reaching interstellar space and drifting lost in the cosmic void. In web development, state represents everything your application needs to remember: user login status, form data, navigation history, and temporary interface states.
In this final part, we'll look over the app we built to rethink how the state is managed, allowing support for browser refresh at any point, and persisting data across user sessions.
As your banking app has evolved from a simple login form into a more sophisticated application, you've likely encountered some common challenges. Refresh the page and users get logged out unexpectedly. Close the browser and all progress disappears. Debug a problem and you're hunting through multiple functions that all modify the same data in different ways.
### Prerequisite
These aren't signs of poor coding they're the natural growing pains that occur when applications reach a certain complexity threshold. Every developer faces these challenges as their apps transition from "proof of concept" to "production ready."
You need to have completed the [data fetching](../3-data/README.md) part of the web app for this lesson. You also need to install [Node.js](https://nodejs.org) and [run the server API](../api/README.md) locally so you can manage account data.
In this lesson, we'll implement a centralized state management system that transforms your banking app into a reliable, professional application. You'll learn to manage data flows predictably, persist user sessions appropriately, and create the smooth user experience that modern web applications require.
You can test that the server is running properly by executing this command in a terminal:
## Prerequisites
Before diving into state management concepts, you'll need to have your development environment properly set up and your banking app foundation in place. This lesson builds directly on the concepts and code from previous parts of this series.
Make sure you have the following components ready before proceeding:
**Required Setup:**
- Complete the [data fetching lesson](../3-data/README.md) - your app should successfully load and display account data
- Install [Node.js](https://nodejs.org) on your system for running the backend API
- Start the [server API](../api/README.md) locally to handle account data operations
**Testing Your Environment:**
Verify that your API server is running correctly by executing this command in a terminal:
```sh
curl http://localhost:5000/api
# -> should return "Bank API v1.0.0" as a result
```
**What this command does:**
- **Sends** a GET request to your local API server
- **Tests** the connection and verifies the server is responding
- **Returns** the API version information if everything is working correctly
---
## Rethink state management
## Diagnosing the Current State Issues
Like Sherlock Holmes examining a crime scene, we need to understand exactly what's happening in our current implementation before we can solve the mystery of disappearing user sessions.
Let's conduct a simple experiment that reveals the underlying state management challenges:
**🧪 Try This Diagnostic Test:**
1. Log into your banking app and navigate to the dashboard
2. Refresh the browser page
3. Observe what happens to your login status
If you're redirected back to the login screen, you've discovered the classic state persistence problem. This behavior occurs because our current implementation stores user data in JavaScript variables that reset with each page load.
In the [previous lesson](../3-data/README.md), we introduced a basic concept of state in our app with the global `account` variable which contains the bank data for the currently logged in user. However, our current implementation has some flaws. Try refreshing the page when you're on the dashboard. What happens?
**Current Implementation Problems:**
There's 3 issues with the current code:
The simple `account` variable from our [previous lesson](../3-data/README.md) creates three significant issues that affect both user experience and code maintainability:
- The state is not persisted, as a browser refresh takes you back to the login page.
- There are multiple functions that modify the state. As the app grows, it can make it difficult to track the changes and it's easy to forget updating one.
- The state is not cleaned up, so when you click on *Logout* the account data is still there even though you're on the login page.
| Problem | Technical Cause | User Impact |
|---------|--------|----------------|
| **Session Loss** | Page refresh clears JavaScript variables | Users must re-authenticate frequently |
| **Scattered Updates** | Multiple functions modify state directly | Debugging becomes increasingly difficult |
| **Incomplete Cleanup** | Logout doesn't clear all state references | Potential security and privacy concerns |
We could update our code to tackle these issues one by one, but it would create more code duplication and make the app more complex and difficult to maintain. Or we could pause for a few minutes and rethink our strategy.
**The Architectural Challenge:**
> What problems are we really trying to solve here?
Like the Titanic's compartmentalized design that seemed robust until multiple compartments flooded simultaneously, fixing these issues individually won't address the underlying architectural problem. We need a comprehensive state management solution.
[State management](https://en.wikipedia.org/wiki/State_management) is all about finding a good approach to solve these two particular problems:
> 💡 **What are we actually trying to accomplish here?**
- How to keep the data flows in an app understandable?
- How to keep the state data always in sync with the user interface (and vice versa)?
[State management](https://en.wikipedia.org/wiki/State_management) is really about solving two fundamental puzzles:
Once you've taken care of these, any other issues you might have may either be fixed already or have become easier to fix. There are many possible approaches for solving these problems, but we'll go with a common solution that consists of **centralizing the data and the ways to change it**. The data flows would go like this:
1. **Where's My Data?**: Keeping track of what information we have and where it's coming from
2. **Is Everyone on the Same Page?**: Making sure what users see matches what's actually happening
**Our Game Plan:**
Instead of chasing our tails, we're going to create a **centralized state management** system. Think of it like having one really organized person in charge of all the important stuff:
![Schema showing the data flows between the HTML, user actions and state](./images/data-flow.png)
> We won't cover here the part where the data automatically triggers the view update, as it's tied to more advanced concepts of [Reactive Programming](https://en.wikipedia.org/wiki/Reactive_programming). It's a good follow-up subject if you're up to a deep dive.
**Understanding this data flow:**
- **Centralizes** all application state in one location
- **Routes** all state changes through controlled functions
- **Ensures** the UI stays synchronized with the current state
- **Provides** a clear, predictable pattern for data management
✅ There are a lot of libraries out there with different approaches to state management, [Redux](https://redux.js.org) being a popular option. Take a look at the concepts and patterns used as it's often a good way to learn what potential issues you may be facing in large web apps and how it can be solved.
> 💡 **Professional Insight**: This lesson focuses on fundamental concepts. For complex applications, libraries like [Redux](https://redux.js.org) provide more advanced state management features. Understanding these core principles will help you master any state management library.
### Task
> ⚠️ **Advanced Topic**: We won't cover automatic UI updates triggered by state changes, as this involves [Reactive Programming](https://en.wikipedia.org/wiki/Reactive_programming) concepts. Consider this an excellent next step for your learning journey!
### Task: Centralize State Structure
Let's begin transforming our scattered state management into a centralized system. This first step establishes the foundation for all the improvements that follow.
We'll start with a bit of refactoring. Replace the `account` declaration:
**Step 1: Create a Central State Object**
Replace the simple `account` declaration:
```js
let account = null;
```
With:
With a structured state object:
```js
let state = {
@ -66,27 +111,76 @@ let state = {
};
```
The idea is to *centralize* all our app data in a single state object. We only have `account` for now in the state so it doesn't change much, but it creates a path for evolutions.
**Here's why this change matters:**
- **Centralizes** all application data in one location
- **Prepares** the structure for adding more state properties later
- **Creates** a clear boundary between state and other variables
- **Establishes** a pattern that scales as your app grows
We also have to update the functions using it. In the `register()` and `login()` functions, replace `account = ...` with `state.account = ...`;
**Step 2: Update State Access Patterns**
At the top of the `updateDashboard()` function, add this line:
Update your functions to use the new state structure:
**In `register()` and `login()` functions**, replace:
```js
account = ...
```
With:
```js
state.account = ...
```
**In `updateDashboard()` function**, add this line at the top:
```js
const account = state.account;
```
This refactoring by itself did not bring much improvements, but the idea was to lay out the foundation for the next changes.
**What these updates accomplish:**
- **Maintains** existing functionality while improving structure
- **Prepares** your code for more sophisticated state management
- **Creates** consistent patterns for accessing state data
- **Establishes** the foundation for centralized state updates
> 💡 **Note**: This refactoring doesn't immediately solve our problems, but it creates the essential foundation for the powerful improvements coming next!
## Implementing Controlled State Updates
With our state centralized, the next step involves establishing controlled mechanisms for data modifications. This approach ensures predictable state changes and easier debugging.
The core principle resembles air traffic control: instead of allowing multiple functions to modify state independently, we'll channel all changes through a single, controlled function. This pattern provides clear oversight of when and how data changes occur.
**Immutable State Management:**
We'll treat our `state` object as [*immutable*](https://en.wikipedia.org/wiki/Immutable_object), meaning we never modify it directly. Instead, each change creates a new state object with the updated data.
## Track data changes
While this approach might initially seem inefficient compared to direct modifications, it provides significant advantages for debugging, testing, and maintaining application predictability.
Now that we have put in place the `state` object to store our data, the next step is to centralize the updates. The goal is to make it easier to keep track of any changes and when they happen.
**Benefits of immutable state management:**
To avoid having changes made to the `state` object, it's also a good practice to consider it [*immutable*](https://en.wikipedia.org/wiki/Immutable_object), meaning that it cannot be modified at all. It also means that you have to create a new state object if you want to change anything in it. By doing this, you build a protection about potentially unwanted [side effects](https://en.wikipedia.org/wiki/Side_effect_(computer_science)), and open up possibilities for new features in your app like implementing undo/redo, while also making it easier to debug. For example, you could log every change made to the state and keep a history of the changes to understand the source of a bug.
| Benefit | Description | Impact |
|---------|-------------|--------|
| **Predictability** | Changes only happen through controlled functions | Easier to debug and test |
| **History Tracking** | Each state change creates a new object | Enables undo/redo functionality |
| **Side Effect Prevention** | No accidental modifications | Prevents mysterious bugs |
| **Performance Optimization** | Easy to detect when state actually changed | Enables efficient UI updates |
In JavaScript, you can use [`Object.freeze()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) to create an immutable version of an object. If you try to make changes to an immutable object, an exception will be raised.
**JavaScript Immutability with `Object.freeze()`:**
✅ Do you know the difference between a *shallow* and a *deep* immutable object? You can read about it [here](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze#What_is_shallow_freeze).
JavaScript provides [`Object.freeze()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) to prevent object modifications:
```js
const immutableState = Object.freeze({ account: userData });
// Any attempt to modify immutableState will throw an error
```
**Breaking down what happens here:**
- **Prevents** direct property assignments or deletions
- **Throws** exceptions if modification attempts are made
- **Ensures** state changes must go through controlled functions
- **Creates** a clear contract for how state can be updated
> 💡 **Deep Dive**: Learn about the difference between *shallow* and *deep* immutable objects in the [MDN documentation](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze#What_is_shallow_freeze). Understanding this distinction is crucial for complex state structures.
### Task
@ -140,43 +234,100 @@ Try registering a new account, logging out and in again to check that everything
> Tip: you can take a look at all state changes by adding `console.log(state)` at the bottom of `updateState()` and opening up the console in your browser's development tools.
## Persist the state
## Implementing Data Persistence
Most web apps need to persist data to be able to work correctly. All the critical data is usually stored on a database and accessed via a server API, like as the user account data in our case. But sometimes, it's also interesting to persist some data on the client app that's running in your browser, for a better user experience or to improve loading performance.
The session loss issue we identified earlier requires a persistence solution that maintains user state across browser sessions. This transforms our application from a temporary experience into a reliable, professional tool.
When you want to persist data in your browser, there are a few important questions you should ask yourself:
Consider how atomic clocks maintain precise time even through power outages by storing critical state in non-volatile memory. Similarly, web applications need persistent storage mechanisms to preserve essential user data across browser sessions and page refreshes.
- *Is the data sensitive?* You should avoid storing any sensitive data on client, such as user passwords.
- *For how long do you need to keep this data?* Do you plan to access this data only for the current session or do you want it to be stored forever?
**Strategic Questions for Data Persistence:**
There are multiple ways of storing information inside a web app, depending on what you want to achieve. For example, you can use the URLs to store a search query, and make it shareable between users. You can also use [HTTP cookies](https://developer.mozilla.org/docs/Web/HTTP/Cookies) if the data needs to be shared with the server, like [authentication](https://en.wikipedia.org/wiki/Authentication) information.
Before implementing persistence, consider these critical factors:
Another option is to use one of the many browser APIs for storing data. Two of them are particularly interesting:
| Question | Banking App Context | Decision Impact |
|----------|-------------------|----------------|
| **Is the data sensitive?** | Account balance, transaction history | Choose secure storage methods |
| **How long should it persist?** | Login state vs. temporary UI preferences | Select appropriate storage duration |
| **Does the server need it?** | Authentication tokens vs. UI settings | Determine sharing requirements |
- [`localStorage`](https://developer.mozilla.org/docs/Web/API/Window/localStorage): a [Key/Value store](https://en.wikipedia.org/wiki/Key%E2%80%93value_database) allowing to persist data specific to the current web site across different sessions. The data saved in it never expires.
- [`sessionStorage`](https://developer.mozilla.org/docs/Web/API/Window/sessionStorage): this one is works the same as `localStorage` except that the data stored in it is cleared when the session ends (when the browser is closed).
**Browser Storage Options:**
Note that both these APIs only allow to store [strings](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String). If you want to store complex objects, you will need to serialize it to the [JSON](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/JSON) format using [`JSON.stringify()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify).
Modern browsers provide several storage mechanisms, each designed for different use cases:
✅ If you want to create a web app that does not work with a server, it's also possible to create a database on the client using the [`IndexedDB` API](https://developer.mozilla.org/docs/Web/API/IndexedDB_API). This one is reserved for advanced use cases or if you need to store significant amount of data, as it's more complex to use.
**Primary Storage APIs:**
### Task
1. **[`localStorage`](https://developer.mozilla.org/docs/Web/API/Window/localStorage)**: Persistent [Key/Value storage](https://en.wikipedia.org/wiki/Key%E2%80%93value_database)
- **Persists** data across browser sessions indefinitely
- **Survives** browser restarts and computer reboots
- **Scoped** to the specific website domain
- **Perfect** for user preferences and login states
2. **[`sessionStorage`](https://developer.mozilla.org/docs/Web/API/Window/sessionStorage)**: Temporary session storage
- **Functions** identically to localStorage during active sessions
- **Clears** automatically when the browser tab closes
- **Ideal** for temporary data that shouldn't persist
3. **[HTTP Cookies](https://developer.mozilla.org/docs/Web/HTTP/Cookies)**: Server-shared storage
- **Automatically** sent with every server request
- **Perfect** for [authentication](https://en.wikipedia.org/wiki/Authentication) tokens
- **Limited** in size and can impact performance
**Data Serialization Requirement:**
We want our users stay logged in until they explicitly click on the *Logout* button, so we'll use `localStorage` to store the account data. First, let's define a key that we'll use to store our data.
Both `localStorage` and `sessionStorage` only store [strings](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String):
```js
// Convert objects to JSON strings for storage
const accountData = { user: 'john', balance: 150 };
localStorage.setItem('account', JSON.stringify(accountData));
// Parse JSON strings back to objects when retrieving
const savedAccount = JSON.parse(localStorage.getItem('account'));
```
**Understanding serialization:**
- **Converts** JavaScript objects to JSON strings using [`JSON.stringify()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify)
- **Reconstructs** objects from JSON using [`JSON.parse()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse)
- **Handles** complex nested objects and arrays automatically
- **Fails** on functions, undefined values, and circular references
> 💡 **Advanced Option**: For complex offline applications with large datasets, consider the [`IndexedDB` API](https://developer.mozilla.org/docs/Web/API/IndexedDB_API). It provides a full client-side database but requires more complex implementation.
### Task: Implement localStorage Persistence
Let's implement persistent storage so users stay logged in until they explicitly log out. We'll use `localStorage` to store account data across browser sessions.
**Step 1: Define Storage Configuration**
```js
const storageKey = 'savedAccount';
```
Then add this line at the end of the `updateState()` function:
**What this constant provides:**
- **Creates** a consistent identifier for our stored data
- **Prevents** typos in storage key references
- **Makes** it easy to change the storage key if needed
- **Follows** best practices for maintainable code
**Step 2: Add Automatic Persistence**
Add this line at the end of the `updateState()` function:
```js
localStorage.setItem(storageKey, JSON.stringify(state.account));
```
With this, the user account data will be persisted and always up-to-date as we centralized previously all our state updates. This is where we begin to benefit from all our previous refactors 🙂.
**Breaking down what happens here:**
- **Converts** the account object to a JSON string for storage
- **Saves** the data using our consistent storage key
- **Executes** automatically whenever state changes occur
- **Ensures** stored data is always synchronized with current state
> 💡 **Architecture Benefit**: Because we centralized all state updates through `updateState()`, adding persistence required only one line of code. This demonstrates the power of good architectural decisions!
**Step 3: Restore State on App Load**
As the data is saved, we also have to take care of restoring it when the app is loaded. Since we'll begin to have more initialization code it may be a good idea to create a new `init` function, that also includes our previous code at the bottom of `app.js`:
Create an initialization function to restore saved data:
```js
function init() {
@ -193,17 +344,49 @@ function init() {
init();
```
Here we retrieve the saved data, and if there's any we update the state accordingly. It's important to do this *before* updating the route, as there might be code relying on the state during the page update.
**Understanding the initialization process:**
- **Retrieves** any previously saved account data from localStorage
- **Parses** the JSON string back into a JavaScript object
- **Updates** the state using our controlled update function
- **Restores** the user's session automatically on page load
- **Executes** before route updates to ensure state is available
**Step 4: Optimize Default Route**
Update the default route to take advantage of persistence:
In `updateRoute()`, replace:
```js
// Replace: return navigate('/login');
return navigate('/dashboard');
```
**Why this change makes sense:**
- **Leverages** our new persistence system effectively
- **Allows** the dashboard to handle authentication checks
- **Redirects** to login automatically if no saved session exists
- **Creates** a more seamless user experience
**Testing Your Implementation:**
We can also make the *Dashboard* page our application default page, as we are now persisting the account data. If no data is found, the dashboard takes care of redirecting to the *Login* page anyways. In `updateRoute()`, replace the fallback `return navigate('/login');` with `return navigate('/dashboard');`.
1. Log into your banking app
2. Refresh the browser page
3. Verify you remain logged in and on the dashboard
4. Close and reopen your browser
5. Navigate back to your app and confirm you're still logged in
Now login in the app and try refreshing the page. You should stay on the dashboard. With that update we've taken care of all our initial issues...
🎉 **Achievement Unlocked**: You've successfully implemented persistent state management! Your app now behaves like a professional web application.
## Refresh the data
## Balancing Persistence with Data Freshness
...But we might also have created a new one. Oops!
Our persistence system successfully maintains user sessions, but introduces a new challenge: data staleness. When multiple users or applications modify the same server data, local cached information becomes outdated.
Go to the dashboard using the `test` account, then run this command on a terminal to create a new transaction:
This situation resembles Viking navigators who relied on both stored star charts and current celestial observations. The charts provided consistency, but navigators needed fresh observations to account for changing conditions. Similarly, our application needs both persistent user state and current server data.
**🧪 Discovering the Data Freshness Problem:**
1. Log into the dashboard using the `test` account
2. Run this command in a terminal to simulate a transaction from another source:
```sh
curl --request POST \
@ -212,15 +395,31 @@ curl --request POST \
http://localhost:5000/api/accounts/test/transactions
```
Try refreshing your dashboard page in the browser now. What happens? Do you see the new transaction?
3. Refresh your dashboard page in the browser
4. Observe whether you see the new transaction
The state is persisted indefinitely thanks to the `localStorage`, but that also means it's never updated until you log out of the app and log in again!
**What this test demonstrates:**
- **Shows** how local storage can become "stale" (outdated)
- **Simulates** real-world scenarios where data changes occur outside your app
- **Reveals** the tension between persistence and data freshness
One possible strategy to fix that is to reload the account data every time the dashboard is loaded, to avoid stall data.
**The Data Staleness Challenge:**
### Task
| Problem | Cause | User Impact |
|---------|-------|-------------|
| **Stale Data** | localStorage never expires automatically | Users see outdated information |
| **Server Changes** | Other apps/users modify the same data | Inconsistent views across platforms |
| **Cache vs. Reality** | Local cache doesn't match server state | Poor user experience and confusion |
**Solution Strategy:**
We'll implement a "refresh on load" pattern that balances the benefits of persistence with the need for fresh data. This approach maintains the smooth user experience while ensuring data accuracy.
Create a new function `updateAccountData`:
### Task: Implement Data Refresh System
We'll create a system that automatically fetches fresh data from the server while maintaining the benefits of our persistent state management.
**Step 1: Create Account Data Updater**
```js
async function updateAccountData() {
@ -238,9 +437,15 @@ async function updateAccountData() {
}
```
This method checks that we are currently logged in then reloads the account data from the server.
**Understanding this function's logic:**
- **Checks** if a user is currently logged in (state.account exists)
- **Redirects** to logout if no valid session is found
- **Fetches** fresh account data from the server using the existing `getAccount()` function
- **Handles** server errors gracefully by logging out invalid sessions
- **Updates** the state with fresh data using our controlled update system
- **Triggers** automatic localStorage persistence through the `updateState()` function
Create another function named `refresh`:
**Step 2: Create Dashboard Refresh Handler**
```js
async function refresh() {
@ -249,7 +454,15 @@ async function refresh() {
}
```
This one updates the account data, then takes care of updating the HTML of the dashboard page. It's what we need to call when the dashboard route is loaded. Update the route definition with:
**What this refresh function accomplishes:**
- **Coordinates** the data refresh and UI update process
- **Waits** for fresh data to be loaded before updating the display
- **Ensures** the dashboard shows the most current information
- **Maintains** a clean separation between data management and UI updates
**Step 3: Integrate with Route System**
Update your route configuration to trigger refresh automatically:
```js
const routes = {
@ -258,7 +471,20 @@ const routes = {
};
```
Try reloading the dashboard now, it should display the updated account data.
**How this integration works:**
- **Executes** the refresh function every time the dashboard route loads
- **Ensures** fresh data is always displayed when users navigate to the dashboard
- **Maintains** the existing route structure while adding data freshness
- **Provides** a consistent pattern for route-specific initialization
**Testing Your Data Refresh System:**
1. Log into your banking app
2. Run the curl command from earlier to create a new transaction
3. Refresh your dashboard page or navigate away and back
4. Verify that the new transaction appears immediately
🎉 **Perfect Balance Achieved**: Your app now combines the smooth experience of persistent state with the accuracy of fresh server data!
## GitHub Copilot Agent Challenge 🚀
@ -268,11 +494,34 @@ Use the Agent mode to complete the following challenge:
**Prompt:** Create an enhanced state management system that includes: 1) A state history array that tracks all previous states, 2) Undo and redo functions that can revert to previous states, 3) UI buttons for undo/redo operations on the dashboard, 4) A maximum history limit of 10 states to prevent memory issues, and 5) Proper cleanup of history when the user logs out. Ensure the undo/redo functionality works with account balance changes and persists across browser refreshes.
## 🚀 Challenge
Learn more about [agent mode](https://code.visualstudio.com/blogs/2025/02/24/introducing-copilot-agent-mode) here.
## 🚀 Challenge: Storage Optimization
Your implementation now handles user sessions, data refresh, and state management effectively. However, consider whether our current approach optimally balances storage efficiency with functionality.
Like chess masters who distinguish between essential pieces and expendable pawns, effective state management requires identifying which data must persist versus which should always be fresh from the server.
**Optimization Analysis:**
Evaluate your current localStorage implementation and consider these strategic questions:
- What's the minimum information required to maintain user authentication?
- Which data changes frequently enough that local caching provides little benefit?
- How can storage optimization improve performance without degrading user experience?
This type of architectural analysis distinguishes experienced developers who consider both functionality and efficiency in their solutions.
**Implementation Strategy:**
- **Identify** the essential data that must persist (likely just user identification)
- **Modify** your localStorage implementation to store only critical session data
- **Ensure** fresh data is always loaded from the server on dashboard visits
- **Test** that your optimized approach maintains the same user experience
Now that we reload the account data every time the dashboard is loaded, do you think we still need to persist *all the account* data?
**Advanced Consideration:**
- **Compare** the trade-offs between storing full account data vs. just authentication tokens
- **Document** your decisions and reasoning for future team members
Try working together to change what is saved and loaded from `localStorage` to only include what is absolutely required for the app to work.
This challenge will help you think like a professional developer who considers both user experience and application efficiency. Take your time to experiment with different approaches!
## Post-Lecture Quiz

@ -1,25 +1,148 @@
# Implement "Add transaction" dialog
# Implement "Add Transaction" Dialog
## Overview
Your banking app now has solid state management and data persistence, but it's missing a crucial feature that real banking apps need: the ability for users to add their own transactions. In this assignment, you'll implement a complete "Add Transaction" dialog that integrates seamlessly with your existing state management system.
This assignment brings together everything you've learned in the four banking lessons: HTML templating, form handling, API integration, and state management.
## Learning Objectives
By completing this assignment, you will:
- **Create** a user-friendly dialog interface for data entry
- **Implement** accessible form design with keyboard and screen reader support
- **Integrate** new features with your existing state management system
- **Practice** API communication and error handling
- **Apply** modern web development patterns to a real-world feature
## Instructions
Our bank app is still missing one important feature: the possibility to enter new transactions.
Using everything that you've learnt in the four previous lessons, implement an "Add transaction" dialog:
### Step 1: Add Transaction Button
**Create** an "Add Transaction" button on your dashboard page that users can easily find and access.
**Requirements:**
- **Place** the button in a logical location on the dashboard
- **Use** clear, action-oriented button text
- **Style** the button to match your existing UI design
- **Ensure** the button is keyboard accessible
### Step 2: Dialog Implementation
Choose one of these two approaches for implementing your dialog:
**Option A: Separate Page**
- **Create** a new HTML template for the transaction form
- **Add** a new route to your routing system
- **Implement** navigation to and from the form page
**Option B: Modal Dialog (Recommended)**
- **Use** JavaScript to show/hide the dialog without leaving the dashboard
- **Implement** using the [`hidden` property](https://developer.mozilla.org/docs/Web/HTML/Global_attributes/hidden) or CSS classes
- **Create** a smooth user experience with proper focus management
### Step 3: Accessibility Implementation
**Ensure** your dialog meets [accessibility standards for modal dialogs](https://developer.paciellogroup.com/blog/2018/06/the-current-state-of-modal-dialog-accessibility/):
**Keyboard Navigation:**
- **Support** Escape key to close the dialog
- **Trap** focus within the dialog when open
- **Return** focus to the trigger button when closed
**Screen Reader Support:**
- **Add** appropriate ARIA labels and roles
- **Announce** dialog opening/closing to screen readers
- **Provide** clear form field labels and error messages
### Step 4: Form Creation
**Design** an HTML form that collects transaction data:
- Add an "Add transaction" button in the dashboard page
- Either create a new page with an HTML template, or use JavaScript to show/hide the dialog HTML without leaving the dashboard page (you can use [`hidden`](https://developer.mozilla.org/docs/Web/HTML/Global_attributes/hidden) property for that, or CSS classes)
- Make sure you handle [keyboard and screen reader accessibility](https://developer.paciellogroup.com/blog/2018/06/the-current-state-of-modal-dialog-accessibility/) for the dialog
- Implement an HTML form to receive input data
- Create JSON data from the form data and send it to the API
- Update the dashboard page with the new data
**Required Fields:**
- **Date**: When the transaction occurred
- **Description**: What the transaction was for
- **Amount**: Transaction value (positive for income, negative for expenses)
Look at the [server API specifications](../api/README.md) to see which API you need to call and what is the expected JSON format.
**Form Features:**
- **Validate** user input before submission
- **Provide** clear error messages for invalid data
- **Include** helpful placeholder text and labels
- **Style** consistently with your existing design
Here's an example result after completing the assignment:
### Step 5: API Integration
**Connect** your form to the backend API:
**Implementation Steps:**
- **Review** the [server API specifications](../api/README.md) for the correct endpoint and data format
- **Create** JSON data from your form inputs
- **Send** the data to the API using appropriate error handling
- **Display** success/failure messages to the user
- **Handle** network errors gracefully
### Step 6: State Management Integration
**Update** your dashboard with the new transaction:
**Integration Requirements:**
- **Refresh** the account data after successful transaction addition
- **Update** the dashboard display without requiring a page reload
- **Ensure** the new transaction appears immediately
- **Maintain** proper state consistency throughout the process
## Technical Specifications
**API Endpoint Details:**
Refer to the [server API documentation](../api/README.md) for:
- Required JSON format for transaction data
- HTTP method and endpoint URL
- Expected response format
- Error response handling
**Expected Result:**
After completing this assignment, your banking app should have a fully functional "Add Transaction" feature that looks and behaves professionally:
![Screenshot showing an example "Add transaction" dialog](./images/dialog.png)
## Rubric
## Testing Your Implementation
**Functional Testing:**
1. **Verify** the "Add Transaction" button is clearly visible and accessible
2. **Test** that the dialog opens and closes properly
3. **Confirm** form validation works for all required fields
4. **Check** that successful transactions appear immediately on the dashboard
5. **Ensure** error handling works for invalid data and network issues
**Accessibility Testing:**
1. **Navigate** through the entire flow using only the keyboard
2. **Test** with a screen reader to ensure proper announcements
3. **Verify** focus management works correctly
4. **Check** that all form elements have appropriate labels
## Evaluation Rubric
| Criteria | Exemplary | Adequate | Needs Improvement |
| -------- | --------- | -------- | ----------------- |
| **Functionality** | Add transaction feature works flawlessly with excellent user experience and follows all best practices from the lessons | Add transaction feature works correctly but may not follow some best practices or has minor usability issues | Add transaction feature is partially working or has significant usability problems |
| **Code Quality** | Code is well-organized, follows established patterns, includes proper error handling, and integrates seamlessly with existing state management | Code works but may have some organization issues or inconsistent patterns with the existing codebase | Code has significant structural issues or doesn't integrate well with existing patterns |
| **Accessibility** | Full keyboard navigation support, screen reader compatibility, and follows WCAG guidelines with excellent focus management | Basic accessibility features implemented but may be missing some keyboard navigation or screen reader features | Limited or no accessibility considerations implemented |
| **User Experience** | Intuitive, polished interface with clear feedback, smooth interactions, and professional appearance | Good user experience with minor areas for improvement in feedback or visual design | Poor user experience with confusing interface or lack of user feedback |
## Additional Challenges (Optional)
Once you've completed the basic requirements, consider these enhancements:
**Enhanced Features:**
- **Add** transaction categories (food, transportation, entertainment, etc.)
- **Implement** input validation with real-time feedback
- **Create** keyboard shortcuts for power users
- **Add** transaction editing and deletion capabilities
**Advanced Integration:**
- **Implement** undo functionality for recently added transactions
- **Add** bulk transaction import from CSV files
- **Create** transaction search and filtering capabilities
- **Implement** data export functionality
| Criteria | Exemplary | Adequate | Needs Improvement |
| -------- | ------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- | --------------------------------------------|
| | Adding a transaction is implemented completely following all best practices seen in the lessons. | Adding a transaction is implement, but not following the best practices seen in the lessons, or working only partially. | Adding a transaction is not working at all. |
These optional features will help you practice more advanced web development concepts and create a more complete banking application!

@ -1,164 +1,371 @@
***
# Using a Code Editor: Mastering [VSCode.dev](https://vscode.dev)
**Welcome!**
This lesson takes you from the basics to advanced use of [VSCode.dev](https://vscode.dev)—the powerful, web-based code editor. Youll learn how to confidently edit code, manage projects, track changes, install extensions, and collaborate like a pro—all from your browser, with zero installation required.
***
## Learning Objectives
By the end of this lesson, youll be able to:
- Efficiently use a code editor on any project, anywhere
- Seamlessly track your work with built-in version control
- Personalize and boost your development workflow with editor customizations and extensions
***
## Prerequisites
To get started, **sign up for a free [GitHub](https://github.com) account**, which lets you manage code repositories and collaborate worldwide. If you dont have an account yet, [create one here](https://github.com/).
***
## Why Use a Web-based Code Editor?
A **code editor** like VSCode.dev is your command center for writing, editing, and managing code. With an intuitive interface, tons of features, and immediate access via the browser, you can:
- Edit projects on any device
- Avoid the hassle of installations
- Collaborate and contribute instantly
Once youre comfortable with VSCode.dev, youll be prepared to tackle coding tasks from anywhere, anytime.
***
## Getting Started with VSCode.dev
Navigate to **[VSCode.dev](https://vscode.dev)**—no install, no downloads. Signing in with GitHub unlocks full access, including syncing your settings, extensions, and repositories. If prompted, connect your GitHub account.
After loading, your workspace will look like this:
![Default VSCode.dev](../images/default-vscode-dev has three core sections from left to right:
- **Activity bar:** The icons such as 🔎 (Search), ⚙️ (Settings), files, source control, etc.
- **Sidebar:** Changes context based on the activity bar icon selected (defaults to *Explorer* to show files).
- **Editor/code area:** The largest section to the right—where youll actually edit and view code.
Click through the icons to explore features, but return to the _Explorer_ to keep your place.
***
## Opening a GitHub Repository
### Method 1: From the Editor
1. Go to [VSCode.dev](https://vscode.dev). Click **"Open Remote Repository."**
![Open remote repository](../images/open-remote-repository use the _Command Palette_ (Ctrl-Shift-P, or Cmd-Shift-P on Mac).
![Palette Menu](../images/palette-menu.pngopen remote repository.”
- Select the option.
- Paste your GitHub repo URL (e.g., `https://github.com/microsoft/Web-Dev-For-Beginners`) and hit Enter.
If successful, youll see the entire project loaded and ready to edit!
***
### Method 2: Instantly via URL
Transform any GitHub repo URL to open directly in VSCode.dev by replacing `github.com` with `vscode.dev/github`.
E.g.:
- GitHub: `https://github.com/microsoft/Web-Dev-For-Beginners`
- VSCode.dev: `https://vscode.dev/github/microsoft/Web-Dev-For-Beginners`
This feature supercharges quick access to ANY project.
***
## Editing Files in Your Project
Once your repo is open, you can:
### 1. **Create a New File**
- In the *Explorer* sidebar, navigate to your desired folder or use the root.
- Click the _New file ..._ icon.
- Name your file, press **Enter**, and your file appears instantly.
![Create a new file](../images/create-new-file 2. **Edit and Save Files**
- Click on a file in the *Explorer* to open it in the code area.
- Make your changes as needed.
- VSCode.dev automatically saves your changes, but you can press Ctrl+S to save manually.
![Edit a file](../images/edit-a-file.png. **Track & Commit Changes with Version Control**
VSCode.dev has integrated **Git** version control!
- Click the _'Source Control'_ icon to view all changes made.
- Files in the `Changes` folder show additions (green) and deletions (red).
![View changes](../images/working-tree.png changes by clicking the `+` next to files to prepare for commit.
- **Discard** unwanted changes by clicking the undo icon.
- Type a clear commit message, then click the checkmark to commit and push.
To return to your repository on GitHub, select the hamburger menu at the top left.
![Stage & commit changes](../images/edit-vscode.dev Up with Extensions
Extensions let you add languages, themes, debuggers, and productivity tools to VSCode.dev—making your coding life easier and more fun.
### Browsing and Managing Extensions
- Click the **Extensions icon** on the activity bar.
- Search for an extension in the _'Search Extensions in Marketplace'_ box.
![Extension details](../images/extension-details:
- **Installed**: All extensions youve added
- **Popular**: Industry favorites
- **Recommended**: Tailored to your workflow
![View extensions](
***
### 1. **Install Extensions**
- Enter the extensions name in search, click it, and review details in the editor.
- Hit the **blue Install button** in the sidebar _or_ in the main code area.
![Install extensions](../images/install-extension 2. **Customize Extensions**
- Find your installed extension.
- Click the **Gear icon** → select _Extension Settings_ to fine-tune behaviors to your liking.
![Modify extension settings](../images/extension-settings 3. **Manage Extensions**
You can:
- **Disable:** Temporarily turn off an extension while keeping it installed
- **Uninstall:** Permanently remove it if no longer needed
Find the extension, hit the Gear icon, and select Disable or Uninstall, or use the blue buttons in the code area.
***
## Assignment
Test your skills: [Create a resume website using vscode.dev](https://github.com/microsoft/Web-Dev-For-Beginners/blob/main/8-code-editor/1-using-a-code-editor/assignment.md)
***
## Further Exploration and Self-Study
- Dive deeper with [the official VSCode Web Docs](https://code.visualstudio.com/docs/editor/vscode-web?WT.mc_id=academic-0000-alfredodeza).
- Explore advanced workspace features, keyboard shortcuts, and settings.
***
**Now youre ready to code, create, and collaborate—from anywhere, on any device, using VSCode.dev!**
# Using a Code Editor: Mastering VSCode.dev
Remember in *The Matrix* when Neo had to plug into a massive computer terminal to access the digital world? Today's web development tools are the opposite story incredibly powerful capabilities accessible from anywhere. VSCode.dev is a browser-based code editor that brings professional development tools to any device with an internet connection.
Just like how the printing press made books accessible to everyone, not just scribes in monasteries, VSCode.dev democratizes coding. You can work on projects from a library computer, a school lab, or anywhere you have browser access. No installations, no "I need my specific setup" limitations.
By the end of this lesson, you'll understand how to navigate VSCode.dev, open GitHub repositories directly in your browser, and use Git for version control all skills that professional developers rely on daily.
## What You'll Learn
After we walk through this together, you'll be able to:
- Navigate VSCode.dev like it's your second home finding everything you need without getting lost
- Open any GitHub repository in your browser and start editing immediately (this one's pretty magical!)
- Use Git to track your changes and save your progress like a pro
- Supercharge your editor with extensions that make coding faster and more fun
- Create and organize project files with confidence
## What You'll Need
The requirements are straightforward:
- A free [GitHub account](https://github.com) (we'll guide you through creation if needed)
- Basic familiarity with web browsers
- The GitHub Basics lesson provides helpful background, though it's not essential
> 💡 **New to GitHub?** Creating an account is free and takes minutes. Like how a library card gives you access to books worldwide, a GitHub account opens doors to code repositories across the internet.
## Why Web-Based Code Editors Matter
Before the internet, scientists at different universities couldn't easily share research. Then came ARPANET in the 1960s, connecting computers across distances. Web-based code editors follow this same principle making powerful tools accessible regardless of your physical location or device.
A code editor serves as your development workspace, where you write, edit, and organize code files. Unlike simple text editors, professional code editors provide syntax highlighting, error detection, and project management features.
VSCode.dev brings these capabilities to your browser:
**Web-based editing advantages:**
| Feature | Description | Practical Benefit |
|---------|-------------|----------|
| **Platform Independence** | Runs on any device with a browser | Work from different computers seamlessly |
| **No Installation Required** | Access through a web URL | Bypass software installation restrictions |
| **Automatic Updates** | Always runs the latest version | Access new features without manual updates |
| **Repository Integration** | Direct connection to GitHub | Edit code without local file management |
**Practical implications:**
- Work continuity across different environments
- Consistent interface regardless of operating system
- Immediate collaboration capabilities
- Reduced local storage requirements
## Exploring VSCode.dev
Just as Marie Curie's laboratory contained sophisticated equipment in a relatively simple space, VSCode.dev packs professional development tools into a browser interface. This web application provides the same core functionality as desktop code editors.
Start by navigating to [vscode.dev](https://vscode.dev) in your browser. The interface loads without downloads or system installations a direct application of cloud computing principles.
### Connecting Your GitHub Account
Like how Alexander Graham Bell's telephone connected distant locations, linking your GitHub account bridges VSCode.dev with your code repositories. When prompted to sign in with GitHub, accepting this connection is recommended.
**GitHub integration provides:**
- Direct access to your repositories within the editor
- Synchronized settings and extensions across devices
- Streamlined saving workflow to GitHub
- Personalized development environment
### Getting to Know Your New Workspace
Once everything loads up, you'll see a beautifully clean workspace that's designed to keep you focused on what matters your code!
![Default VSCode.dev interface](../images/default-vscode-dev.png)
**Here's your tour of the neighborhood:**
- **Activity Bar** (that strip on the left): Your main navigation with Explorer 📁, Search 🔍, Source Control 🌿, Extensions 🧩, and Settings ⚙️
- **Sidebar** (the panel next to it): Changes to show you relevant info based on what you've selected
- **Editor Area** (the big space in the middle): This is where the magic happens your main coding area
**Take a moment to explore:**
- Click around those Activity Bar icons and see what each one does
- Notice how the sidebar updates to show different information pretty neat, right?
- The Explorer view (📁) is probably where you'll spend most of your time, so get comfortable with it
## Opening GitHub Repositories
Before the internet, researchers had to physically travel to libraries to access documents. GitHub repositories work similarly they're collections of code stored remotely. VSCode.dev eliminates the traditional step of downloading repositories to your local machine before editing.
This capability enables immediate access to any public repository for viewing, editing, or contributing. Here are two methods for opening repositories:
### Method 1: The Point-and-Click Way
This is perfect when you're starting fresh in VSCode.dev and want to open a specific repository. It's straightforward and beginner-friendly:
**Here's how to do it:**
1. Head to [vscode.dev](https://vscode.dev) if you're not already there
2. Look for the "Open Remote Repository" button on the welcome screen and click it
![Open remote repository](../images/open-remote-repository.png)
3. Paste in any GitHub repository URL (try this one: `https://github.com/microsoft/Web-Dev-For-Beginners`)
4. Hit Enter and watch the magic happen!
**Pro tip - The Command Palette shortcut:**
Want to feel like a coding wizard? Try this keyboard shortcut: Ctrl+Shift+P (or Cmd+Shift+P on Mac) to open the Command Palette:
![Command Palette](../images/palette-menu.png)
**The Command Palette is like having a search engine for everything you can do:**
- Type "open remote" and it'll find the repository opener for you
- It remembers repositories you've opened recently (super handy!)
- Once you get used to it, you'll feel like you're coding at lightning speed
- It's basically VSCode.dev's version of "Hey Siri, but for coding"
### Method 2: URL Modification Technique
Like how HTTP and HTTPS use different protocols while maintaining the same domain structure, VSCode.dev uses a URL pattern that mirrors GitHub's addressing system. Any GitHub repository URL can be modified to open directly in VSCode.dev.
**URL transformation pattern:**
| Repository Type | GitHub URL | VSCode.dev URL |
|----------------|---------------------|----------------|
| **Public Repository** | `github.com/microsoft/Web-Dev-For-Beginners` | `vscode.dev/github/microsoft/Web-Dev-For-Beginners` |
| **Personal Project** | `github.com/your-username/my-project` | `vscode.dev/github/your-username/my-project` |
| **Any Accessible Repo** | `github.com/their-username/awesome-repo` | `vscode.dev/github/their-username/awesome-repo` |
**Implementation:**
- Replace `github.com` with `vscode.dev/github`
- Maintain all other URL components unchanged
- Works with any publicly accessible repository
- Provides immediate editing access
> 💡 **Life-changing tip**: Bookmark the VSCode.dev versions of your favorite repositories. I have bookmarks like "Edit My Portfolio" and "Fix Documentation" that take me straight to editing mode!
**Which method should you use?**
- **The interface way**: Great when you're exploring or can't remember exact repository names
- **The URL trick**: Perfect for lightning-fast access when you know exactly where you're going
## Working with Files and Projects
Now that you've got a repository open, let's start building! VSCode.dev gives you everything you need to create, edit, and organize your code files. Think of it as your digital workshop every tool is right where you need it.
Let's dive into the everyday tasks that'll make up most of your coding workflow.
### Creating New Files
Like organizing blueprints in an architect's office, file creation in VSCode.dev follows a structured approach. The system supports all standard web development file types.
**File creation process:**
1. Navigate to the target folder in the Explorer sidebar
2. Hover over the folder name to reveal the "New File" icon (📄+)
3. Enter the filename including the appropriate extension (`style.css`, `script.js`, `index.html`)
4. Press Enter to create the file
![Creating a new file](../images/create-new-file.png)
**Naming conventions:**
- Use descriptive names that indicate file purpose
- Include file extensions for proper syntax highlighting
- Follow consistent naming patterns throughout projects
- Use lowercase letters and hyphens instead of spaces
### Editing and Saving Files
This is where the real fun begins! VSCode.dev's editor is packed with helpful features that make coding feel smooth and intuitive. It's like having a really smart writing assistant, but for code.
**Your editing workflow:**
1. Click on any file in the Explorer to open it in the main area
2. Start typing and watch VSCode.dev help you out with colors, suggestions, and error spotting
3. Save your work with Ctrl+S (Windows/Linux) or Cmd+S (Mac) though it auto-saves too!
![Editing files in VSCode.dev](../images/edit-a-file.png)
**The cool stuff that happens while you code:**
- Your code gets beautifully color-coded so it's easy to read
- VSCode.dev suggests completions as you type (like autocorrect, but way smarter)
- It catches typos and errors before you even save
- You can have multiple files open in tabs, just like in a browser
- Everything saves automatically in the background
> ⚠️ **Quick tip**: Even though auto-save has your back, hitting Ctrl+S or Cmd+S is still a good habit. It immediately saves everything and triggers some extra helpful features like error checking.
### Version Control with Git
Like how archaeologists create detailed records of excavation layers, Git tracks changes in your code over time. This system preserves project history and enables you to revert to previous versions when needed. VSCode.dev includes integrated Git functionality.
**Source Control interface:**
1. Access the Source Control panel via the 🌿 icon in the Activity Bar
2. Modified files appear in the "Changes" section
3. Color coding indicates change types: green for additions, red for deletions
![Viewing changes in Source Control](../images/working-tree.png)
**Saving your work (the commit workflow):**
```mermaid
flowchart TD
A[Make changes to files] --> B[View changes in Source Control]
B --> C[Stage changes by clicking +]
C --> D[Write descriptive commit message]
D --> E[Click checkmark to commit]
E --> F[Changes pushed to GitHub]
```
**Here's your step-by-step process:**
- Click the "+" icon next to files you want to save (this "stages" them)
- Double-check that you're happy with all your staged changes
- Write a short note explaining what you did (this is your "commit message")
- Click the checkmark button to save everything to GitHub
- If you change your mind about something, the undo icon lets you discard changes
**Writing good commit messages (this is easier than you think!):**
- Just describe what you did, like "Add contact form" or "Fix broken navigation"
- Keep it short and sweet think tweet length, not essay
- Start with action words like "Add", "Fix", "Update", or "Remove"
- **Good examples**: "Add responsive navigation menu", "Fix mobile layout issues", "Update colors for better accessibility"
> 💡 **Quick navigation tip**: Use the hamburger menu (☰) at the top left to jump back to your GitHub repository and see your committed changes online. It's like a portal between your editing environment and your project's home on GitHub!
## Enhancing Functionality with Extensions
Just as a craftsperson's workshop contains specialized tools for different tasks, VSCode.dev can be customized with extensions that add specific capabilities. These community-developed plugins address common development needs like code formatting, live preview, and enhanced Git integration.
The extension marketplace hosts thousands of free tools created by developers worldwide. Each extension solves particular workflow challenges, allowing you to build a personalized development environment suited to your specific needs and preferences.
### Finding Your Perfect Extensions
The extension marketplace is really well organized, so you won't get lost trying to find what you need. It's designed to help you discover both specific tools and cool stuff you didn't even know existed!
**Getting to the marketplace:**
1. Click the Extensions icon (🧩) in the Activity Bar
2. Browse around or search for something specific
3. Click on anything that looks interesting to learn more about it
![Extension marketplace interface](../images/extensions.png)
**What you'll see in there:**
| Section | What's Inside | Why It's Helpful |
|----------|---------|----------|
| **Installed** | Extensions you've already added | Your personal coding toolkit |
| **Popular** | The crowd favorites | What most developers swear by |
| **Recommended** | Smart suggestions for your project | VSCode.dev's helpful recommendations |
**What makes browsing easy:**
- Each extension shows ratings, download counts, and real user reviews
- You get screenshots and clear descriptions of what each one does
- Everything's clearly marked with compatibility info
- Similar extensions are suggested so you can compare options
### Installing Extensions (It's Super Easy!)
Adding new powers to your editor is as simple as clicking a button. Extensions install in seconds and start working immediately no restarts, no waiting around.
**Here's all you need to do:**
1. Search for what you want (try searching "live server" or "prettier")
2. Click on one that looks good to see more details
3. Read through what it does and check out the ratings
4. Hit that blue "Install" button and you're done!
![Installing extensions](../images/install-extension.gif)
**What happens behind the scenes:**
- The extension downloads and sets itself up automatically
- New features appear in your interface right away
- Everything starts working immediately (seriously, it's that fast!)
- If you're signed in, the extension syncs to all your devices
**Some extensions I'd recommend starting with:**
- **Live Server**: See your website update in real-time as you code (this one's magical!)
- **Prettier**: Makes your code look clean and professional automatically
- **Auto Rename Tag**: Change one HTML tag and its partner updates too
- **Bracket Pair Colorizer**: Color-codes your brackets so you never get lost
- **GitLens**: Supercharges your Git features with tons of helpful info
### Customizing Your Extensions
Most extensions come with settings you can tweak to make them work exactly how you like. Think of it like adjusting the seat and mirrors in a car everyone has their preferences!
**Tweaking extension settings:**
1. Find your installed extension in the Extensions panel
2. Look for the little gear icon (⚙️) next to its name and click it
3. Choose "Extension Settings" from the dropdown
4. Adjust things until they feel just right for your workflow
![Customizing extension settings](../images/extension-settings.png)
**Common things you might want to adjust:**
- How your code gets formatted (tabs vs spaces, line length, etc.)
- Which keyboard shortcuts trigger different actions
- What file types the extension should work with
- Turning specific features on or off to keep things clean
### Keeping Your Extensions Organized
As you discover more cool extensions, you'll want to keep your collection tidy and running smoothly. VSCode.dev makes this really easy to manage.
**Your extension management options:**
| What You Can Do | When It's Helpful | Pro Tip |
|--------|---------|----------|
| **Disable** | Testing if an extension is causing issues | Better than uninstalling if you might want it back |
| **Uninstall** | Completely removing extensions you don't need | Keeps your environment clean and fast |
| **Update** | Getting the latest features and bug fixes | Usually happens automatically, but worth checking |
**How I like to manage extensions:**
- Every few months, I review what I've installed and remove anything I'm not using
- I keep extensions updated so I get the latest improvements and security fixes
- If something seems slow, I temporarily disable extensions to see if one of them is the culprit
- I read the update notes when extensions get major updates sometimes there are cool new features!
> ⚠️ **Performance tip**: Extensions are awesome, but having too many can slow things down. Focus on the ones that genuinely make your life easier and don't be afraid to uninstall ones you never use.
## GitHub Copilot Agent Challenge 🚀
Like the structured approach NASA uses for space missions, this challenge involves systematic application of VSCode.dev skills in a complete workflow scenario.
**Objective:** Demonstrate proficiency with VSCode.dev by establishing a comprehensive web development workflow.
**Project requirements:** Using Agent mode assistance, complete these tasks:
1. Fork an existing repository or create a new one
2. Establish a functional project structure with HTML, CSS, and JavaScript files
3. Install and configure three development-enhancing extensions
4. Practice version control with descriptive commit messages
5. Experiment with feature branch creation and modification
6. Document the process and learnings in a README.md file
This exercise consolidates all VSCode.dev concepts into a practical workflow that can be applied to future development projects.
Learn more about [agent mode](https://code.visualstudio.com/blogs/2025/02/24/introducing-copilot-agent-mode) here.
## Assignment
Time to take these skills for a real test drive! I've got a hands-on project that'll let you practice everything we've covered: [Create a resume website using VSCode.dev](./assignment.md)
This assignment walks you through building a professional resume website entirely in your browser. You'll use all the VSCode.dev features we've explored, and by the end, you'll have both a great-looking website and solid confidence in your new workflow.
## Keep Exploring and Growing Your Skills
You've got a solid foundation now, but there's so much more cool stuff to discover! Here are some resources and ideas for taking your VSCode.dev skills to the next level:
**Official docs worth bookmarking:**
- [VSCode Web Documentation](https://code.visualstudio.com/docs/editor/vscode-web?WT.mc_id=academic-0000-alfredodeza) The complete guide to browser-based editing
- [GitHub Codespaces](https://docs.github.com/en/codespaces) For when you want even more power in the cloud
**Cool features to experiment with next:**
- **Keyboard Shortcuts**: Learn the key combos that'll make you feel like a coding ninja
- **Workspace Settings**: Set up different environments for different types of projects
- **Multi-root Workspaces**: Work on multiple repositories at the same time (super handy!)
- **Terminal Integration**: Access command-line tools right in your browser
**Ideas for practicing:**
- Jump into some open-source projects and contribute using VSCode.dev it's a great way to give back!
- Try out different extensions to find your perfect setup
- Create project templates for the types of sites you build most often
- Practice Git workflows like branching and merging these skills are gold in team projects
---
**You've mastered browser-based development!** 🎉 Like how the invention of portable instruments allowed scientists to conduct research in remote locations, VSCode.dev enables professional coding from any internet-connected device.
These skills reflect current industry practices many professional developers use cloud-based development environments for their flexibility and accessibility. You've learned a workflow that scales from individual projects to large team collaborations.
Apply these techniques to your next development project! 🚀

@ -1,252 +1,576 @@
# Create a resume-website using vscode.dev
# Create a Resume Website Using VSCode.dev
_How cool would it be to have a recruiter ask for your resume and you send them a url?_ 😎
Transform your career prospects by building a professional resume website that showcases your skills and experience in an interactive, modern format. Instead of sending traditional PDFs, imagine providing recruiters with a sleek, responsive website that demonstrates both your qualifications and your web development capabilities.
<!----
TODO: add an optional image
![Using a code editor](../../sketchnotes/webdev101-vscode-dev.png)
> Sketchnote by [Author name](https://example.com)
---->
This hands-on assignment puts all your VSCode.dev skills into practice while creating something genuinely useful for your career. You'll experience the complete web development workflow from repository creation to deployment all within your browser.
<!---
## Pre-Lecture Quiz
[Pre-lecture quiz](https://ff-quizzes.netlify.app/web/quiz/3)
---->
By completing this project, you'll have a professional online presence that can be easily shared with potential employers, updated as your skills grow, and customized to match your personal brand. This is exactly the kind of practical project that demonstrates real-world web development skills.
## Objectives
## Learning Objectives
After this assignment, you'll learn how to:
After completing this assignment, you'll be able to:
- Create a website to showcase your resume
- **Create** and manage a complete web development project using VSCode.dev
- **Structure** a professional website using semantic HTML elements
- **Style** responsive layouts with modern CSS techniques
- **Implement** interactive features using basic web technologies
- **Deploy** a live website accessible via a shareable URL
- **Demonstrate** version control best practices throughout the development process
### Prerequisites
## Prerequisites
1. A GitHub account. Navigate to [GitHub](https://github.com/) and create an account if you haven't already.
Before starting this assignment, ensure you have:
## Steps
- A GitHub account (create one at [github.com](https://github.com/) if needed)
- Completion of the VSCode.dev lesson covering interface navigation and basic operations
- Basic understanding of HTML structure and CSS styling concepts
**Step 1:** Create a new GitHub Repository and give it a name `my-resume`
## Project Setup and Repository Creation
Let's start by setting up your project foundation. This process mirrors real-world development workflows where projects begin with proper repository initialization and structure planning.
**Step 2** Create an `index.html` file in your repository. We will add at least one file while still on github.com because you cannot open an empty repository on vscode.dev
### Step 1: Create Your GitHub Repository
Click the `creating a new file` link, type in the name `index.html` and select the `Commit new file` button
Setting up a dedicated repository ensures your project is properly organized and version-controlled from the beginning.
![Create a new file on github.com](../images/new-file-github.com.png)
1. **Navigate** to [GitHub.com](https://github.com) and sign in to your account
2. **Click** the green "New" button or the "+" icon in the top-right corner
3. **Name** your repository `my-resume` (or choose a personalized name like `john-smith-resume`)
4. **Add** a brief description: "Professional resume website built with HTML and CSS"
5. **Select** "Public" to make your resume accessible to potential employers
6. **Check** "Add a README file" to create an initial project description
7. **Click** "Create repository" to finalize setup
> 💡 **Repository Naming Tip**: Use descriptive, professional names that clearly indicate the project's purpose. This helps when sharing with employers or during portfolio reviews.
**Step 3:** Open [VSCode.dev](https://vscode.dev) and select the `Open Remote Repository` button
### Step 2: Initialize Project Structure
Copy the url to the repository you just created for your resume site and paste it in the input box:
Since VSCode.dev requires at least one file to open a repository, we'll create our main HTML file directly on GitHub before switching to the web editor.
_Replace `your-username` with your github username_
1. **Click** "creating a new file" link in your new repository
2. **Type** `index.html` as the filename
3. **Add** this initial HTML structure:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Your Name - Professional Resume</title>
</head>
<body>
<h1>Your Name</h1>
<p>Professional Resume Website</p>
</body>
</html>
```
https://github.com/your-username/my-resume
```
✅ If successful, you'll see your project and the index.html file open up on the text editor on the browser.
4. **Write** a commit message: "Add initial HTML structure"
5. **Click** "Commit new file" to save your changes
![Creating initial file on GitHub](../images/new-file-github.com.png)
**Here's what this initial setup accomplishes:**
- **Establishes** proper HTML5 document structure with semantic elements
- **Includes** viewport meta tag for responsive design compatibility
- **Sets** a descriptive page title that appears in browser tabs
- **Creates** the foundation for professional content organization
## Working in VSCode.dev
Now that your repository foundation is established, let's transition to VSCode.dev for the main development work. This web-based editor provides all the tools needed for professional web development.
### Step 3: Open Your Project in VSCode.dev
1. **Navigate** to [vscode.dev](https://vscode.dev) in a new browser tab
2. **Click** "Open Remote Repository" on the welcome screen
3. **Copy** your repository URL from GitHub and paste it into the input field
Format: `https://github.com/your-username/my-resume`
*Replace `your-username` with your actual GitHub username*
![Create a new file](../images/project-on-vscode.dev.png)
4. **Press** Enter to load your project
**Success indicator**: You should see your project files in the Explorer sidebar and `index.html` available for editing in the main editor area.
**Step 4:** Open the `index.html` file, paste in the code below on your code area and save
![Project loaded in VSCode.dev](../images/project-on-vscode.dev.png)
**What you'll see in the interface:**
- **Explorer sidebar**: **Displays** your repository files and folder structure
- **Editor area**: **Shows** the content of selected files for editing
- **Activity bar**: **Provides** access to features like Source Control and Extensions
- **Status bar**: **Indicates** connection status and current branch information
### Step 4: Build Your Resume Content
Replace the placeholder content in `index.html` with a comprehensive resume structure. This HTML provides the foundation for a professional presentation of your qualifications.
<details>
<summary><b>HTML code responsible for the content on your resume website.</b></summary>
<summary><b>Complete HTML Resume Structure</b></summary>
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="style.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
<title>Your Name - Professional Resume</title>
</head>
<body>
<header id="header">
<h1>Your Full Name</h1>
<hr>
<p class="role">Your Professional Title</p>
<hr>
</header>
<html>
<head>
<link href="style.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
<title>Your Name Goes Here!</title>
</head>
<body>
<header id="header">
<!-- resume header with your name and title -->
<h1>Your Name Goes Here!</h1>
<hr>
Your Role!
<hr>
</header>
<main>
<article id="mainLeft">
<section>
<h2>CONTACT</h2>
<!-- contact info including social media -->
<p>
<i class="fa fa-envelope" aria-hidden="true"></i>
<a href="mailto:username@domain.top-level domain">Write your email here</a>
</p>
<p>
<i class="fab fa-github" aria-hidden="true"></i>
<a href="github.com/yourGitHubUsername">Write your username here!</a>
</p>
<p>
<i class="fab fa-linkedin" aria-hidden="true"></i>
<a href="linkedin.com/yourLinkedInUsername">Write your username here!</a>
</p>
</section>
<section>
<h2>SKILLS</h2>
<!-- your skills -->
<ul>
<li>Skill 1!</li>
<li>Skill 2!</li>
<li>Skill 3!</li>
<li>Skill 4!</li>
</ul>
</section>
<section>
<h2>EDUCATION</h2>
<!-- your education -->
<h3>Write your course here!</h3>
<p>
Write your institution here!
</p>
<p>
Start - End Date
</p>
</section>
</article>
<article id="mainRight">
<section>
<h2>ABOUT</h2>
<!-- about you -->
<p>Write a blurb about yourself!</p>
</section>
<section>
<h2>WORK EXPERIENCE</h2>
<!-- your work experience -->
<h3>Job Title</h3>
<p>
Organization Name Goes Here | Start Month End Month
</p>
<ul>
<li>Task 1 - Write what you did!</li>
<li>Task 2 - Write what you did!</li>
<li>Write the outcomes/impact of your contribution</li>
</ul>
<h3>Job Title 2</h3>
<p>
Organization Name Goes Here | Start Month End Month
</p>
<ul>
<li>Task 1 - Write what you did!</li>
<li>Task 2 - Write what you did!</li>
<li>Write the outcomes/impact of your contribution</li>
</ul>
</section>
</article>
</main>
</body>
</html>
<main>
<article id="mainLeft">
<section>
<h2>CONTACT</h2>
<p>
<i class="fa fa-envelope" aria-hidden="true"></i>
<a href="mailto:your.email@domain.com">your.email@domain.com</a>
</p>
<p>
<i class="fab fa-github" aria-hidden="true"></i>
<a href="https://github.com/your-username">github.com/your-username</a>
</p>
<p>
<i class="fab fa-linkedin" aria-hidden="true"></i>
<a href="https://linkedin.com/in/your-profile">linkedin.com/in/your-profile</a>
</p>
</section>
<section>
<h2>SKILLS</h2>
<ul>
<li>HTML5 & CSS3</li>
<li>JavaScript (ES6+)</li>
<li>Responsive Web Design</li>
<li>Version Control (Git)</li>
<li>Problem Solving</li>
</ul>
</section>
<section>
<h2>EDUCATION</h2>
<h3>Your Degree or Certification</h3>
<p>Institution Name</p>
<p>Start Date - End Date</p>
</section>
</article>
<article id="mainRight">
<section>
<h2>ABOUT</h2>
<p>Write a compelling summary that highlights your passion for web development, key achievements, and career goals. This section should give employers insight into your personality and professional approach.</p>
</section>
<section>
<h2>WORK EXPERIENCE</h2>
<div class="job">
<h3>Job Title</h3>
<p class="company">Company Name | Start Date End Date</p>
<ul>
<li>Describe a key accomplishment or responsibility</li>
<li>Highlight specific skills or technologies used</li>
<li>Quantify impact where possible (e.g., "Improved efficiency by 25%")</li>
</ul>
</div>
<div class="job">
<h3>Previous Job Title</h3>
<p class="company">Previous Company | Start Date End Date</p>
<ul>
<li>Focus on transferable skills and achievements</li>
<li>Demonstrate growth and learning progression</li>
<li>Include any leadership or collaboration experiences</li>
</ul>
</div>
</section>
<section>
<h2>PROJECTS</h2>
<div class="project">
<h3>Project Name</h3>
<p>Brief description of what the project accomplishes and technologies used.</p>
<a href="#" target="_blank">View Project</a>
</div>
</section>
</article>
</main>
</body>
</html>
```
</details>
Add your resume details to replace the _placeholder text_ on the html code
**Customization guidelines:**
- **Replace** all placeholder text with your actual information
- **Adjust** sections based on your experience level and career focus
- **Add** or remove sections as needed (e.g., Certifications, Volunteer Work, Languages)
- **Include** links to your actual profiles and projects
**Step 5:** Hover on the My-Resume folder, click the `New File ...` icon and create 2 new files in your project: `style.css` and `codeswing.json` files
### Step 5: Create Supporting Files
**Step 6:** Open the `style.css` file, paste in the code below and save
Professional websites require organized file structures. Create the CSS stylesheet and configuration files needed for a complete project.
<details>
<summary><b>CSS code to format the layout of the site.</b></summary>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
font-size: 16px;
max-width: 960px;
margin: auto;
}
h1 {
font-size: 3em;
letter-spacing: .6em;
padding-top: 1em;
padding-bottom: 1em;
}
h2 {
font-size: 1.5em;
padding-bottom: 1em;
}
h3 {
font-size: 1em;
padding-bottom: 1em;
}
main {
display: grid;
grid-template-columns: 40% 60%;
margin-top: 3em;
}
header {
text-align: center;
margin: auto 2em;
}
section {
margin: auto 1em 4em 2em;
}
i {
margin-right: .5em;
}
p {
margin: .2em auto
}
hr {
border: none;
background-color: lightgray;
height: 1px;
}
h1, h2, h3 {
font-weight: 100;
margin-bottom: 0;
}
#mainLeft {
border-right: 1px solid lightgray;
}
1. **Hover** over your project folder name in the Explorer sidebar
2. **Click** the "New File" icon (📄+) that appears
3. **Create** these files one at a time:
- `style.css` (for styling and layout)
- `codeswing.json` (for the preview extension configuration)
**Creating the CSS file (`style.css`):**
<details>
<summary><b>Professional CSS Styling</b></summary>
```css
/* Modern Resume Styling */
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
font-size: 16px;
line-height: 1.6;
max-width: 960px;
margin: 0 auto;
padding: 20px;
color: #333;
background-color: #f9f9f9;
}
/* Header Styling */
header {
text-align: center;
margin-bottom: 3em;
padding: 2em;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
h1 {
font-size: 3em;
letter-spacing: 0.1em;
margin-bottom: 0.2em;
font-weight: 300;
}
.role {
font-size: 1.3em;
font-weight: 300;
margin: 1em 0;
}
/* Main Content Layout */
main {
display: grid;
grid-template-columns: 35% 65%;
gap: 3em;
margin-top: 3em;
background: white;
padding: 2em;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
/* Typography */
h2 {
font-size: 1.4em;
font-weight: 600;
margin-bottom: 1em;
color: #667eea;
border-bottom: 2px solid #667eea;
padding-bottom: 0.3em;
}
h3 {
font-size: 1.1em;
font-weight: 600;
margin-bottom: 0.5em;
color: #444;
}
/* Section Styling */
section {
margin-bottom: 2.5em;
}
#mainLeft {
border-right: 1px solid #e0e0e0;
padding-right: 2em;
}
/* Contact Links */
section a {
color: #667eea;
text-decoration: none;
transition: color 0.3s ease;
}
section a:hover {
color: #764ba2;
text-decoration: underline;
}
/* Icons */
i {
margin-right: 0.8em;
width: 20px;
text-align: center;
color: #667eea;
}
/* Lists */
ul {
list-style: none;
padding-left: 0;
}
li {
margin: 0.5em 0;
padding: 0.3em 0;
position: relative;
}
li:before {
content: "▸";
color: #667eea;
margin-right: 0.5em;
}
/* Work Experience */
.job, .project {
margin-bottom: 2em;
padding-bottom: 1.5em;
border-bottom: 1px solid #f0f0f0;
}
.company {
font-style: italic;
color: #666;
margin-bottom: 0.5em;
}
/* Responsive Design */
@media (max-width: 768px) {
main {
grid-template-columns: 1fr;
gap: 2em;
}
#mainLeft {
border-right: none;
border-bottom: 1px solid #e0e0e0;
padding-right: 0;
padding-bottom: 2em;
}
h1 {
font-size: 2.2em;
}
body {
padding: 10px;
}
}
/* Print Styles */
@media print {
body {
background: white;
color: black;
font-size: 12pt;
}
header {
background: none;
color: black;
box-shadow: none;
}
main {
box-shadow: none;
}
}
```
</details>
**Step 6:** Open the `codeswing.json` file, paste in the code below and save
**Creating the configuration file (`codeswing.json`):**
{
```json
{
"scripts": [],
"styles": []
}
}
```
**Understanding the CSS features:**
- **Uses** CSS Grid for responsive, professional layout structure
- **Implements** modern color schemes with gradient headers
- **Includes** hover effects and smooth transitions for interactivity
- **Provides** responsive design that works on all device sizes
- **Adds** print-friendly styles for PDF generation
### Step 6: Install and Configure Extensions
Extensions enhance your development experience by providing live preview capabilities and improved workflow tools. The CodeSwing extension is particularly useful for web development projects.
**Installing the CodeSwing Extension:**
1. **Click** the Extensions icon (🧩) in the Activity Bar
2. **Search** for "CodeSwing" in the marketplace search box
3. **Select** the CodeSwing extension from the search results
4. **Click** the blue "Install" button
![Installing the CodeSwing extension](../images/install-extension.gif)
**What CodeSwing provides:**
- **Enables** live preview of your website as you edit
- **Displays** changes in real-time without manual refresh
- **Supports** multiple file types including HTML, CSS, and JavaScript
- **Provides** an integrated development environment experience
**Immediate results after installation:**
Once CodeSwing is installed, you'll see a live preview of your resume website appear in the editor. This allows you to see exactly how your site looks as you make changes.
![CodeSwing extension showing live preview](../images/after-codeswing-extension-pb.png)
**Understanding the enhanced interface:**
- **Split view**: **Shows** your code on one side and live preview on the other
- **Real-time updates**: **Reflects** changes immediately as you type
- **Interactive preview**: **Allows** you to test links and interactions
- **Mobile simulation**: **Provides** responsive design testing capabilities
### Step 7: Version Control and Publishing
Now that your resume website is complete, use Git to save your work and make it available online.
**Committing your changes:**
1. **Click** the Source Control icon (🌿) in the Activity Bar
2. **Review** all the files you've created and modified in the "Changes" section
3. **Stage** your changes by clicking the "+" icon next to each file
4. **Write** a descriptive commit message, such as:
- "Add complete resume website with responsive design"
- "Implement professional styling and content structure"
5. **Click** the checkmark (✓) to commit and push your changes
**Effective commit message examples:**
- "Add professional resume content and styling"
- "Implement responsive design for mobile compatibility"
- "Update contact information and project links"
> 💡 **Professional Tip**: Good commit messages help track your project's evolution and demonstrate attention to detail qualities that employers value.
**Accessing your published site:**
Once committed, you can return to your GitHub repository using the hamburger menu (☰) in the top-left corner. Your resume website is now version-controlled and ready for deployment or sharing.
**Step 7:** Install the `Codeswing extension` to visualize the resume website on the code area.
## Results and Next Steps
Click the _`Extensions`_ icon on the activity bar and type in Codeswing. Either click the _blue install button_ on the expanded activity bar to install or use the install button that appears on the code area once you select the extension to load additional information. Immediately after installing the extension, observe your code area to see the changes to your project 😃
**Congratulations! 🎉** You've successfully created a professional resume website using VSCode.dev. Your project demonstrates:
**Technical skills demonstrated:**
- **Repository management**: Created and organized a complete project structure
- **Web development**: Built a responsive website using modern HTML5 and CSS3
- **Version control**: Implemented proper Git workflow with meaningful commits
- **Tool proficiency**: Effectively used VSCode.dev's interface and extension system
![Install extensions](../images/install-extension.gif)
**Professional outcomes achieved:**
- **Online presence**: A shareable URL that showcases your qualifications
- **Modern format**: An interactive alternative to traditional PDF resumes
- **Demonstrable skills**: Concrete evidence of your web development capabilities
- **Easy updates**: A foundation you can continuously improve and customize
This is what you will see on your on your screen after you install the extension.
### Deployment Options
![Codeswing extension in action](../images/after-codeswing-extension-pb.png)
To make your resume accessible to employers, consider these hosting options:
If you are satisfied with the changes you made, hover on the `Changes` folder and click the `+` button to stage the changes.
**GitHub Pages (Recommended):**
1. Go to your repository Settings on GitHub
2. Scroll to the "Pages" section
3. Select "Deploy from a branch" and choose "main"
4. Your site will be available at `https://your-username.github.io/my-resume`
Type in a commit message _(A description of the change you have made to the project)_ and commit your changes by clicking the `check`.Once done working on your project, select the hamburger menu icon at the top left to return to the repository on GitHub.
**Alternative platforms:**
- **Netlify**: Automatic deployment with custom domains
- **Vercel**: Fast deployment with modern hosting features
- **GitHub Codespaces**: Development environment with built-in preview
Congratulations 🎉 You have just created your resume website using vscode.dev in few steps.
## 🚀 Challenge
### Enhancement Suggestions
Open a remote repository you have permissions to make changes and update some files. Next, try creating a new branch with your changes and make a Pull Request.
Continue developing your skills by adding these features:
<!----
## Post-Lecture Quiz
[Post-lecture quiz](https://ff-quizzes.netlify.app/web/quiz/4)
---->
**Technical improvements:**
- **JavaScript interactivity**: Add smooth scrolling or interactive elements
- **Dark mode toggle**: Implement theme switching for user preference
- **Contact form**: Enable direct communication from potential employers
- **SEO optimization**: Add meta tags and structured data for better search visibility
**Content enhancements:**
- **Project portfolio**: Link to GitHub repositories and live demonstrations
- **Skills visualization**: Create progress bars or skill rating systems
- **Testimonials section**: Include recommendations from colleagues or instructors
- **Blog integration**: Add a blog section to showcase your learning journey
## GitHub Copilot Agent Challenge 🚀
Use the Agent mode to complete the following challenge:
**Description:** Enhance your resume website with advanced features that demonstrate professional web development capabilities and modern design principles.
**Prompt:** Building on your existing resume website, implement these advanced features:
1. Add a dark/light theme toggle with smooth transitions
2. Create an interactive skills section with animated progress bars
3. Implement a contact form with form validation
4. Add a projects portfolio section with hover effects and modal popups
5. Include a blog section with at least 3 sample posts about your learning journey
6. Optimize for SEO with proper meta tags, structured data, and performance
7. Deploy the enhanced site using GitHub Pages or Netlify
8. Document all new features in your README.md with screenshots
Your enhanced website should demonstrate mastery of modern web development practices including responsive design, JavaScript interactivity, and professional deployment workflows.
## Challenge Extension
Ready to take your skills further? Try these advanced challenges:
**📱 Mobile-First Redesign:** Completely rebuild your site using a mobile-first approach with CSS Grid and Flexbox
**🔍 SEO Optimization:** Implement comprehensive SEO including meta tags, structured data, and performance optimization
**🌐 Multi-language Support:** Add internationalization features to support multiple languages
**📊 Analytics Integration:** Add Google Analytics to track visitor engagement and optimize your content
**🚀 Performance Optimization:** Achieve perfect Lighthouse scores across all categories
## Review & Self Study
Read more about [VSCode.dev](https://code.visualstudio.com/docs/editor/vscode-web?WT.mc_id=academic-0000-alfredodeza) and some of its other features.
Expand your knowledge with these resources:
**Advanced VSCode.dev Features:**
- [VSCode.dev Documentation](https://code.visualstudio.com/docs/editor/vscode-web?WT.mc_id=academic-0000-alfredodeza) - Complete guide to web-based editing
- [GitHub Codespaces](https://docs.github.com/en/codespaces) - Cloud development environments
**Web Development Best Practices:**
- **Responsive Design**: Study CSS Grid and Flexbox for modern layouts
- **Accessibility**: Learn WCAG guidelines for inclusive web design
- **Performance**: Explore tools like Lighthouse for optimization
- **SEO**: Understand search engine optimization fundamentals
**Professional Development:**
- **Portfolio Building**: Create additional projects to showcase diverse skills
- **Open Source**: Contribute to existing projects to gain collaboration experience
- **Networking**: Share your resume website in developer communities for feedback
- **Continuous Learning**: Stay updated with web development trends and technologies
---
**Your Next Steps:** Share your resume website with friends, family, or mentors for feedback. Use their suggestions to iterate and improve your design. Remember, this project is not just a resume it's a demonstration of your growth as a web developer!

File diff suppressed because it is too large Load Diff

@ -1,7 +1,5 @@
# Run code
## Set up
Create virtual environment
@ -14,15 +12,23 @@ source ./venv/bin/activate
## Install dependencies
```sh
pip install openai flask flask-cors
pip install openai fastapi uvicorn python-dotenv
```
## Run API
```sh
# Method 1: Direct execution
python api.py
# Method 2: Using uvicorn
uvicorn api:app --host 0.0.0.0 --port 5000 --reload
```
## Test API
Visit the interactive API documentation at: `http://localhost:5000/docs`
## Run frontend
Make sure you stand in the frontend folder

@ -1,56 +1,84 @@
from flask import Flask, request, jsonify
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from llm import call_llm
from flask_cors import CORS
import logging
app = Flask(__name__)
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Create FastAPI application
app = FastAPI(
title="AI Chat API",
description="A high-performance API for AI-powered chat applications",
version="1.0.0"
)
# Configure CORS (allow all origins for development; restrict in production)
CORS(app, resources={r"/*": {"origins": "*"}})
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Set up logging
logging.basicConfig(level=logging.INFO)
# Pydantic models for request/response validation
class ChatMessage(BaseModel):
message: str
class ChatResponse(BaseModel):
response: str
class HealthResponse(BaseModel):
status: str
service: str
@app.route("/", methods=["GET"])
def index():
@app.get("/")
async def index():
"""Root endpoint for API status."""
return jsonify({
return {
"status": "ok",
"message": "Welcome to the Chat Project API"
})
"message": "Welcome to the Chat Project API",
"docs": "/docs",
"health": "/health"
}
@app.route("/health", methods=["GET"])
def health():
@app.get("/health", response_model=HealthResponse)
async def health():
"""Health check endpoint."""
return jsonify({"status": "healthy"}), 200
return HealthResponse(status="healthy", service="ai-chat-api")
@app.route("/test", methods=["GET"])
def test():
@app.get("/test")
async def test():
"""Simple test endpoint."""
return jsonify({"result": "Test successful"}), 200
return {"result": "Test successful"}
@app.route("/hello", methods=["POST"])
def hello():
@app.post("/hello", response_model=ChatResponse)
async def hello(chat_message: ChatMessage):
"""
Chat endpoint.
Expects JSON: { "message": "your message" }
Returns: { "response": "LLM response" }
"""
try:
data = request.get_json(force=True)
message = data.get("message", "").strip()
message = chat_message.message.strip()
if not message:
logging.warning("No message provided in request.")
return jsonify({"error": "No message provided."}), 400
logger.warning("Empty message provided in request.")
raise HTTPException(status_code=400, detail="Message cannot be empty")
logging.info(f"Received message: {message}")
logger.info(f"Received message: {message}")
response = call_llm(message, "You are a helpful assistant.")
return jsonify({"response": response}), 200
logger.info("LLM response generated successfully")
return ChatResponse(response=response)
except HTTPException:
raise
except Exception as e:
logging.error(f"Error in /hello endpoint: {e}")
return jsonify({"error": "Internal server error."}), 500
logger.error(f"Error in /hello endpoint: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
if __name__ == "__main__":
# Run the app with debug mode for development
app.run(host="0.0.0.0", port=5000, debug=True)
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=5000, reload=True)
Loading…
Cancel
Save