bank project updates

softchris-patch-9
chris 1 month ago
parent 17da96140c
commit 99d78c0f41

@ -1,14 +1,28 @@
# Build a Banking App Part 1: HTML Templates and Routes in a Web App
## Pre-Lecture Quiz
Modern web applications have revolutionized how we interact with digital services, from social media platforms to online banking systems. Unlike traditional websites that reload entire pages for each interaction, today's web apps provide seamless, app-like experiences that respond instantly to user actions. This smooth interaction is powered by sophisticated techniques that update only specific parts of the page, creating the fluid experience users expect.
[Pre-lecture quiz](https://ff-quizzes.netlify.app/web/quiz/41)
| 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 |
**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
### Introduction
In this lesson, you'll discover how to build the foundation of a single-page application (SPA) by creating a banking app with multiple screens that never require a full page reload. You'll learn how HTML templates work as reusable building blocks, how JavaScript routing enables navigation between different views, and how the browser's history API creates a natural browsing experience. These concepts form the backbone of modern web development and are used by popular frameworks like React, Vue, and Angular.
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.
By the end of this lesson, you'll have built a functional multi-screen banking application that demonstrates professional web development techniques. You'll understand how to create smooth user experiences that rival native mobile apps, setting the foundation for more advanced web development skills. Let's start building something amazing!
## Pre-Lecture Quiz
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.
[Pre-lecture quiz](https://ff-quizzes.netlify.app/web/quiz/41)
### Prerequisite
@ -32,9 +46,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
HTML templates are one of the most powerful features for building dynamic web applications. Instead of creating separate HTML files for each screen in your app, templates allow you to define reusable HTML structures that can be dynamically loaded and displayed as needed. This approach significantly improves performance and provides a smoother user experience.
Think of templates as blueprints for different parts of your application. Just like an architect uses blueprints to construct different rooms in a building, you'll use HTML templates to construct different screens in your web app. The browser doesn't display these templates initially they remain hidden until your JavaScript code 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:
@ -51,9 +91,13 @@ We'll create a bank app with two screens: the login page and the dashboard. Firs
<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 +110,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 +147,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
## Displaying Templates with JavaScript
Now that we have our HTML templates defined, we need to bring them to life with JavaScript. Templates by themselves are invisible they're like blueprints waiting to be built into actual structures. JavaScript provides the tools to select a template, create a working copy of it, and insert it into the visible part of your webpage.
The process of displaying a template involves three essential steps that work together to transform hidden template code into visible, interactive content. Understanding this process is crucial because it's the foundation of how modern web frameworks manage dynamic content.
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 +174,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 +207,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 +225,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 +238,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 the backbone of modern web applications it's what makes your app feel like a cohesive experience rather than a collection of disconnected pages. When you navigate through a professional web app like Gmail or Twitter, the URL changes to reflect where you are, but the page doesn't reload. This seamless experience is powered by client-side routing, which maps URLs to specific content or views.
In traditional multi-page websites, the web server handles routing automatically by serving different HTML files based on the URL path. However, in single-page applications (SPAs), we need to implement this routing logic ourselves using JavaScript. This gives us complete control over how navigation works and enables the smooth, app-like experience users expect.
```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 +303,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 +324,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
Great routing logic is only half the equation we also need intuitive navigation that allows users to move between screens effortlessly. In traditional websites, clicking a link causes the entire page to reload, which breaks the smooth experience we're trying to create. Instead, we'll implement programmatic navigation that updates the URL and content seamlessly.
Effective navigation in a single-page application requires coordinating two essential actions: updating the browser's URL to reflect the current screen, and displaying the appropriate content. This coordination ensures that users can bookmark specific screens, use the browser's back button, and share URLs that work exactly as expected.
> 🏗️ **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 +364,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 +377,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 +396,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 +421,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 +433,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
## Handling the Browser's Back and Forward Buttons
Users expect web applications to behave like traditional websites when it comes to browser navigation. When someone clicks the back button, they should return to the previous screen, and the forward button should work just as intuitively. However, single-page applications need special handling to make this work correctly.
The browser's navigation buttons interact with the history stack that we've been building with `history.pushState`. Each time we navigate to a new screen, we add an entry to this stack. When users click back or forward, the browser moves through this stack, but our application needs to respond to these changes and update 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 +505,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:
@ -303,6 +536,12 @@ Use the Agent mode to complete the following 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 +550,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,731 @@
[Pre-lecture quiz](https://ff-quizzes.netlify.app/web/quiz/43)
### Introduction
Forms are the gateway between users and your web application. They're how users share their information, create accounts, and interact with your app's features. Think of forms as the digital equivalent of paperwork they collect essential information, but unlike traditional paperwork, web forms can provide instant feedback, validate data in real-time, and create seamless user 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.
In this lesson, you'll transform your static banking app into an interactive application where users can register for accounts and log in securely. You'll discover how to build robust forms using modern HTML5 features, handle user input with JavaScript, and communicate with a server API to store and retrieve user data. These skills form the foundation of user authentication and data management in web development.
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.
By the end of this lesson, you'll have created a complete user registration and login system, complete with form validation and server communication. You'll understand how forms work behind the scenes and be equipped to build secure, user-friendly input systems for any web application. Let's dive in and bring your banking app to life!
### Prerequisite
## Prerequisites
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.
Before diving into form development, let's ensure your development environment is properly configured. This lesson builds directly on the foundation you created in the previous session, so having everything set up correctly is crucial for success.
**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.
### Required Setup
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.
| 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 |
You can test that the server is running properly by executing this command in a terminal:
> 💡 **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.
```sh
### 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 the primary way users interact with web applications. They provide a structured way to collect, validate, and submit user data. Think of forms as digital contracts they define what information you need and how users can provide it safely and efficiently.
Modern forms go far beyond simple text boxes. They include specialized input types, built-in validation, and accessibility features that create better user experiences while maintaining security and data integrity.
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.
### Essential Form Elements
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:
**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">` |
> 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.
> 💡 **Modern HTML5 Advantage**: Using specific input types provides automatic validation, appropriate mobile keyboards, and better accessibility support without additional JavaScript!
✅ 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.
### Button Types and Behavior
```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:
**Understanding button types:**
- **Triggers** form submission when `type="submit"` (default behavior)
- **Resets** all form fields to their initial values when `type="reset"`
- **Requires** JavaScript for custom behavior when `type="button"`
- `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]
```
**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
- 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.
> 🎯 **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.
> [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.
### Creating the Registration Form
Now we'll add a second form for the registration, just below the previous one:
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:**
✅ Can you navigate and interact with the forms using only a keyboard? How would you do that?
| 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 |
## Submitting data to the server
> 🎯 **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.
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?
## Understanding Form Submission Methods
Did you notice the change in your browser's URL section?
Form submission is how your application communicates with the server to store and retrieve user data. Understanding the different submission methods helps you choose the right approach for different scenarios and build more secure, efficient applications.
Let's explore what happens when users interact with your forms and how you can control that behavior for optimal user experience.
### Default Form Behavior
First, let's observe what happens with basic form submission:
**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]
```
- 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
**Understanding the differences:**
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.
| 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 |
> 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.
**What each method accomplishes:**
- **GET method**: Appends form data to the URL as query parameters
- **POST method**: Sends form data in the HTTP request body
- **GET limitations**: Data visible in URL, limited size, cached by browsers
- **POST advantages**: Hidden data transmission, supports file uploads, better for sensitive information
### Task
> 💡 **Best Practice**: Use `GET` for search forms and filters (data retrieval), use `POST` for user registration, login, and data creation.
Add `action` and `method` properties to the registration form:
### Configuring Form Submission
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
✅ Try registering again with the same name. What happens?
**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
## Submitting data without reloading the page
## Modern Form Handling with JavaScript
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).
Single-page applications (SPAs) provide seamless user experiences by avoiding page reloads during user interactions. Instead of traditional form submission that redirects users away from your app, you'll use JavaScript to handle form data and communicate with the server while keeping users in your 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:
This approach gives you complete control over the user experience, allowing you to show loading states, handle errors gracefully, and update the interface dynamically based on server responses.
- Retrieve the form data
- Convert and encode the form data to a suitable format
- Create the HTTP request and send it to the server
### Why Avoid Page Reloads?
```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!
```
### Task
**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
Replace the registration form `action` with:
### 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
### Implementing JavaScript Form Handling
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();
});
});
```
**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"
// }
```
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.
**What this modern approach accomplishes:**
- **Captures** all form fields automatically, including checkboxes and file inputs
- **Handles** various input types without manual coding
- **Maintains** proper data types and formatting
- **Simplifies** form data extraction with minimal code
- **Supports** dynamic forms with changing field structures
### 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 protects both your users and your application by ensuring data quality before it reaches the server. Modern web development uses a layered validation approach combining HTML5 built-in validation, custom JavaScript validation, and server-side verification for comprehensive data integrity.
- `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.
Effective validation provides immediate feedback to users, prevents common errors, and creates a smooth, professional user experience while reducing server load and potential security issues.
> 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.
**Understanding validation feedback:**
- **Green indicators** show users when their input is correct
- **Red indicators** highlight fields that need attention
- **Focus states** provide clear navigation feedback
- **Consistent styling** creates predictable user experiences
> 💡 **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
```
**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
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.
> 🛡️ **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.
---

@ -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
Data is the lifeblood of modern web applications. Every time you check your bank balance, refresh your social media feed, or search for a product online, you're witnessing the power of dynamic data fetching and display. Unlike traditional websites that required full page reloads to show new information, modern web applications can seamlessly update content in real-time, creating smooth and responsive user experiences.
[Pre-lecture quiz](https://ff-quizzes.netlify.app/web/quiz/45)
In this lesson, you'll discover how to transform your static banking app into a dynamic, data-driven application. You'll learn to fetch user account information from a server, handle asynchronous operations with modern JavaScript, and dynamically update your HTML to display real bank account data. This represents a crucial shift from hard-coded content to live, server-driven information.
### Introduction
By the end of this lesson, you'll understand the fundamental concepts behind single-page applications and have the skills to create web apps that feel fast, responsive, and professional. Let's dive into the world of asynchronous data fetching and dynamic content updates!
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"
```
**Testing your setup:**
- **Verifies** that Node.js is properly installed on your system
- **Confirms** the API server is running and accessible
- **Establishes** the connection between your app and the data source
---
## 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.
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*.
Let's explore how traditional websites worked compared to the dynamic, responsive applications we build today.
### Traditional Multi-Page Applications (MPA)
In the early days of the web, every user interaction required a complete page reload. When you clicked a link or submitted a form, the browser would request an entirely new HTML page from the server, causing the screen to flash white and interrupting the user's experience.
```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).
**Understanding the MPA approach:**
- **Reloads** the entire page for every data update or navigation
- **Interrupts** user interaction with visible page refreshes
- **Transfers** more data since complete HTML pages are sent
- **Creates** a less smooth, desktop-app-like experience
### Modern Single-Page Applications (SPA)
Modern web applications use a technique called AJAX (Asynchronous JavaScript and XML) to fetch only the data they need, without reloading the entire page. This creates much smoother, app-like experiences that users have come to expect.
```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.
**Here's what makes SPAs superior:**
- **Updates** only the parts of the page that need to change
- **Maintains** smooth user interactions without interruption
- **Transfers** less data by sending only JSON instead of full HTML
- **Provides** desktop-application-like responsiveness and speed
### The Evolution to Modern Fetch API
> 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.
While AJAX originally used the [`XMLHttpRequest`](https://developer.mozilla.org/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest) API, modern browsers provide the more elegant [`Fetch` API](https://developer.mozilla.org/docs/Web/API/Fetch_API). The Fetch API uses promises and is specifically designed for handling JSON data - perfect for modern web applications.
### Task
| 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 |
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:
> 💡 **Browser Compatibility**: While all modern browsers support the Fetch API, you can always check the [compatibility table on caniuse.com](https://caniuse.com/fetch) to verify support for your target browsers.
>
**What you need to know:**
- **Supports** all current browser versions (Chrome, Firefox, Safari, Edge)
- **Requires** polyfills only for Internet Explorer and very old browsers
- **Provides** the foundation for modern async/await JavaScript patterns
```js
### Implementing User Login and Data Retrieval
Now that you understand how modern web applications fetch data, let's implement these concepts in your banking app. You'll create a login function that authenticates users and retrieves their account information from the server - all without reloading the page.
We'll build this functionality step by step, starting with a basic login function and then adding 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.
**Breaking down what happens here:**
- **Declares** an `async` function to handle asynchronous operations
- **Retrieves** the login form element using `getElementById()`
- **Extracts** the username value from the form input field
- **Accesses** form controls by their `name` attribute as properties of the form
> 💡 **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.
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:
#### Step 2: Create the Account Data Fetching Function
```js
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:
```javascript
async function getAccount(user) {
try {
const response = await fetch('//localhost:5000/api/accounts/' + encodeURIComponent(user));
@ -63,15 +146,39 @@ 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
> ⚠️ **Security Consideration**: The `encodeURIComponent()` function escapes special characters that could break URLs or cause security issues. What problems might occur if a username contained characters like `#`, `&`, or spaces without proper encoding?
>
**Security implications to consider:**
- **Prevents** URL parsing errors when usernames contain special characters
- **Blocks** potential injection attacks through malformed URLs
- **Ensures** consistent server-side processing of user data
- **Maintains** proper HTTP request formatting standards
#### Understanding HTTP GET Requests
By default, the `fetch` API creates a [`GET`](https://developer.mozilla.org/docs/Web/HTTP/Methods/GET) HTTP request, which is perfect for retrieving data. Unlike POST requests (used for creating accounts), GET requests:
| 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 |
`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?
#### Step 3: Complete the Login Function
Let's now update our `login` function to use `getAccount`:
Now let's integrate the account fetching function into your login process. This creates a complete user authentication and data retrieval workflow:
```js
```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 +191,259 @@ 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.
**Step by step, here's what's happening:**
- **Retrieves** the username from the login form
- **Calls** the `getAccount` function and waits for the server response
- **Checks** for errors and logs them if they occur (we'll improve this soon)
- **Stores** the account data in a global variable for app-wide access
- **Navigates** to the dashboard page to display the account information
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:
> 🎯 **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.
```js
#### Step 4: Set Up Global Data Storage
Your app needs a place to store the account data so it can be accessed from different parts of your application. Add this global variable at the top of your `app.js` file:
```javascript
// Global variable to store current user 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.
**Understanding global state management:**
- **Stores** account data accessible throughout the entire application
- **Initializes** as `null` to indicate no user is currently logged in
- **Updates** when users successfully log in or register
- **Provides** a single source of truth for user account information
#### Step 5: Connect the Login Function to Your Form
Finally, we need to call our `login` function when the login form is submitted, by modifying the HTML:
Update your HTML form to call the login function when submitted:
```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 accomplishes:**
- **Prevents** the default form submission that would reload the page
- **Triggers** your custom JavaScript login function instead
- **Maintains** the single-page application experience
- **Allows** for custom error handling and user feedback
Before moving on to the next part, we can also complete the `register` function by adding this at the bottom of the function:
#### Step 6: Enhance Your Registration Function
```js
For consistency, update your `register` function to also store account data and navigate to the dashboard:
```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]
```
**Test your login system:**
1. **Register** a new account to ensure the API is working
2. **Try logging in** with the same credentials
3. **Check the browser console** for any error messages
4. **Verify** that you're redirected to the dashboard after successful login
#### Understanding Cross-Origin Requests
You might be wondering how our web app can communicate with the API server when they're running on different ports. This involves an important web security concept that every developer should understand.
> 🔒 **Security Insight**: By default, browsers enforce the "same-origin policy" which only allows web pages to make requests to the same domain and port they were loaded from. This prevents malicious websites from accessing data from other sites.
>
**Here's what this means:**
- **Web app** runs on `localhost:3000` (your development server)
- **API server** runs on `localhost:5000` (your backend)
- **Browsers** would normally block this cross-origin communication
- **CORS** (Cross-Origin Resource Sharing) allows controlled exceptions
Our setup works because the API server includes special [CORS headers](https://developer.mozilla.org/docs/Web/HTTP/CORS) that tell the browser: "It's okay to allow requests from localhost:3000." This is a crucial concept for modern web development where frontend and backend often run on different 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).
> 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)
## Displaying Dynamic Data in Your HTML
## Update HTML to display data
Now that your app can successfully fetch user data from the server, it's time to make that data visible to your users. This is where the magic happens - transforming raw JSON data into meaningful, interactive content that users can see and understand.
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:
Let's explore the essential techniques for dynamically updating HTML content with JavaScript, focusing on safe and efficient methods that create professional user experiences.
- 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.
### Understanding DOM Manipulation Methods
When working with dynamic content, you have several tools available for updating HTML elements. Each method serves different purposes and has specific security considerations:
| Method | Purpose | Best Use Case | Security Level |
|--------|---------|---------------|----------------|
| `textContent` | Update text safely | Displaying user data, simple text | ✅ Safe |
| `createElement()` + `append()` | Create new elements | Building complex structures | ✅ Safe |
| `innerHTML` | Set HTML content | ⚠️ Should be avoided | ❌ XSS vulnerable |
#### Safe Text Content Updates
The [`textContent`](https://developer.mozilla.org/docs/Web/API/Node/textContent) property is your go-to method for safely displaying user data:
```javascript
// Safe way to update text content
const balanceElement = document.getElementById('balance');
balanceElement.textContent = account.balance;
```
**Key benefits of textContent:**
- **Prevents** XSS attacks by treating all content as plain text
- **Removes** all existing child elements when updated
- **Provides** an efficient way to clear elements by setting to empty string `''`
- **Ensures** safe display of user-generated content
#### Creating Dynamic HTML Elements
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:
```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);
```
- 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.
**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 [`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.
> ⚠️ **Security Warning**: While the [`innerHTML`](https://developer.mozilla.org/docs/Web/API/Element/innerHTML) property can update HTML content, it should be avoided because it's vulnerable to [cross-site scripting (XSS)](https://developer.mozilla.org/docs/Glossary/Cross-site_scripting) attacks.
>
**Why innerHTML is dangerous:**
- **Executes** any JavaScript code included in the HTML string
- **Allows** malicious users to inject harmful scripts
- **Creates** security vulnerabilities in production applications
- **Better alternatives** exist for all common use cases
### Task
### Implementing User-Friendly Error Handling
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.
Right now, when login fails, error messages only appear in the browser console - invisible to your users. Let's fix this by creating a professional error display system that provides clear feedback and works well for all users, including those using screen readers.
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>`:
This improvement transforms your app from a developer tool into a polished user experience.
#### Step 1: Add Error Display Elements to Your HTML
First, add a placeholder element in your login form where error messages can appear. Place it just before the login button for optimal visibility:
```html
...
<div id="loginError"></div>
<!-- Add this error display area to your login form -->
<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.
**Here's what this code does:**
- **Creates** an empty container that's invisible until needed
- **Positions** the error message where users naturally look for feedback
- **Includes** the `role="alert"` attribute for screen reader accessibility
- **Provides** a unique `id` for easy JavaScript targeting
Go back to the `app.js` file and create a new helper function `updateElement`:
#### Step 2: Create a Reusable Update Function
```js
Next, create a helper function that can update any element's text content. This promotes code reusability and consistency:
```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:
**Understanding this utility function:**
- **Accepts** an element ID and text content as parameters
- **Finds** the target element using `getElementById()`
- **Updates** the element's text content safely using `textContent`
- **Provides** a reusable pattern for updating any page element
#### Step 3: Display Login Errors to Users
Update your login function to show errors visually instead of just logging them:
```js
```javascript
// Replace the console.log with user-visible error display
if (data.error) {
return updateElement('loginError', data.error);
}
```
Now if you try to login with an invalid account, you should see something like this:
**What this improvement accomplishes:**
- **Displays** error messages where users can see them
- **Replaces** developer-only console messages with user-friendly feedback
- **Stops** the login process when errors occur
- **Provides** immediate, actionable feedback to users
Now when you test with an invalid account, you'll see a clear error message:
![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: Ensure Accessibility with Live Regions
The error message now appears visually, but screen reader users might miss it since it's added dynamically. The `role="alert"` attribute creates a [Live Region](https://developer.mozilla.org/docs/Web/Accessibility/ARIA/ARIA_Live_Regions) that announces changes to assistive technologies:
```html
<div id="loginError" role="alert"></div>
```
Implement the same behavior for the `register` function errors (don't forget to update the HTML).
**Why accessibility matters:**
- **Announces** error messages to screen reader users immediately
- **Ensures** all users receive important feedback
- **Complies** with web accessibility standards (WCAG)
- **Creates** inclusive experiences for users with disabilities
#### Step 5: Apply the Same Pattern to Registration
For consistency, implement identical error handling in your registration form:
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);
}
```
## Display information on the dashboard
**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
Using the same techniques we've just seen, we'll also take care of displaying the account information on the dashboard page.
## Building Your Dynamic Dashboard
This is what an account object received from the server looks like:
Now comes the exciting part - transforming your static dashboard into a dynamic, data-driven interface that displays real account information. You'll apply the DOM manipulation techniques you've learned to create a professional banking dashboard that updates in real-time.
Let's start by understanding the data structure you'll be working with, then build the dashboard step by step.
### Understanding the Account Data Structure
When your app successfully retrieves account data from the server, it receives a JSON object with all the information needed to display a complete banking dashboard:
```json
{
@ -183,15 +455,32 @@ 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.
**Breaking down this data structure:**
- **`user`**: The account holder's username for personalization
- **`currency`**: The currency symbol for proper formatting
- **`description`**: A human-readable account description
- **`balance`**: The current account balance as a number
- **`transactions`**: An array of transaction objects with complete details
> 💡 **Development Tip**: For testing purposes, you can use the pre-existing `test` account that's already populated with sample data. This lets you see the dashboard in action without creating multiple test accounts.
>
**What the test account provides:**
- **Sample data** that demonstrates all dashboard features
- **Realistic transactions** showing both income and expenses
- **Consistent testing** environment for development
- **Quick verification** that your implementation works correctly
### Creating the Dashboard Display Elements
### Task
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.
Let's start by replacing the "Balance" section in the HTML to add placeholder elements:
#### 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 +488,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 +518,73 @@ 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.
> 💰 **Number Formatting**: The [`toFixed(2)`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed) method ensures the balance always displays with exactly 2 decimal places, making it look professional and consistent with banking standards (e.g., "75.00" instead of "75").
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: Integrate Dashboard Updates with Navigation
Add this code to the end of the `updateRoute()` function:
To ensure your dashboard updates every time it's displayed, you need to connect the `updateDashboard()` function to your routing system.
```js
If you completed the [lesson 1 assignment](../1-template-route/assignment.md), this integration should be straightforward. Otherwise, use this implementation:
Add this code 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 definition 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.
**Understanding this routing enhancement:**
- **Checks** if a route has an initialization function defined
- **Calls** the initialization function when the route is loaded
- **Ensures** the dashboard displays fresh data every time it's visited
- **Maintains** separation between routing logic and dashboard updates
#### Testing Your Dashboard
## Create table rows dynamically with HTML templates
After implementing these changes, test your dashboard:
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.
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
We'll use a similar approach to display the list of transactions in the HTML table.
Your dashboard should now display dynamic account information that updates based on the logged-in user's data!
## Creating Dynamic Transaction Lists with HTML Templates
One of the most powerful features of your banking dashboard will be the transaction history. Instead of hardcoding transaction data, you'll use HTML templates to dynamically generate table rows for each transaction. This technique scales beautifully - whether a user has 3 transactions or 300, your code handles them all efficiently.
You've already used HTML templates for navigation in the [first lesson](../1-template-route/README.md). Now you'll apply the same powerful technique to create repeating content that adapts to your data.
```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 +596,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
Then, add this `id` property to the `<tbody>` element of the table within the dashboard template to make it easier to find using JavaScript:
### Step 2: Prepare Your Table for Dynamic Content
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
Now create a function that transforms transaction data into HTML elements:
```js
```javascript
function createTransactionRow(transaction) {
const template = document.getElementById('transaction');
const transactionRow = template.content.cloneNode(true);
@ -288,9 +631,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
```js
### Step 4: Generate Multiple Transaction Rows Efficiently
Add this code to your `updateDashboard()` function to display all transactions:
```javascript
const transactionsRows = document.createDocumentFragment();
for (const transaction of account.transactions) {
const transactionRow = createTransactionRow(transaction);
@ -299,11 +652,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
> ⚡ **Performance Tip**: [`document.createDocumentFragment()`](https://developer.mozilla.org/docs/Web/API/Document/createDocumentFragment) creates an in-memory container that doesn't trigger layout recalculations until attached to the main DOM. This dramatically improves performance when adding multiple elements.
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:
### Step 5: Enhance the Update Function for Mixed Content
```js
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 +673,28 @@ 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
### Testing Your Dynamic Transaction List
Now test your implementation:
1. **Log in** with the `test` account to see sample transaction data
2. **Navigate** to the dashboard
3. **Verify** that transaction rows appear with proper formatting
4. **Check** that dates, descriptions, and amounts display correctly
You should now see a complete 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:**
- **Transformed** static HTML into dynamic, data-driven content
- **Created** a scalable system that handles any number of transactions
- **Applied** professional web development patterns using templates
- **Built** an efficient, performant solution for repetitive content
---

@ -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,102 @@
[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 one of the most crucial concepts in modern web development, yet it's often overlooked until applications become complex and difficult to maintain. Think of state as the "memory" of your web application it's all the data your app needs to remember: user information, current page content, form inputs, and much more. As your banking app grows from a simple login form to a full-featured financial dashboard, managing this state becomes increasingly challenging.
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.
In the previous lessons, you've built a functional banking application with user authentication and data fetching. However, you may have noticed some frustrating behaviors: refreshing the page logs you out, data doesn't persist between sessions, and tracking changes across different parts of your app can become confusing. These are classic signs that your application needs a more sophisticated approach to state management.
### Prerequisite
In this final lesson, you'll transform your banking app from a simple prototype into a robust, production-ready application. You'll learn how to implement centralized state management, persist data across browser sessions, and create a seamless user experience that works exactly as users expect. By the end, you'll understand why state management is considered one of the foundational skills of professional web development.
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.
## Prerequisites
You can test that the server is running properly by executing this command in a terminal:
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
## Rethink State Management
Now that you have a working banking app, it's time to examine some critical flaws in our current approach. While the app functions correctly during normal use, you'll discover several frustrating user experience issues that professional applications simply cannot have.
Let's identify the problems with our current state management by testing the user experience:
**🧪 Try This Experiment:**
1. Log into your banking app and navigate to the dashboard
2. Refresh the page in your browser
3. Observe what happens to your logged-in state
**Current State Management Problems:**
Our simple `account` variable approach from the [previous lesson](../3-data/README.md) has three critical flaws:
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?
| Problem | Impact | User Experience |
|---------|--------|----------------|
| **No Persistence** | Browser refresh logs users out | Frustrating for users who accidentally refresh |
| **Scattered Updates** | Multiple functions modify state | Difficult to track changes and debug issues |
| **No Cleanup** | Logout doesn't clear data | Security risk and confusing behavior |
There's 3 issues with the current code:
**The Root Challenge:**
- 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.
Rather than fixing these issues one by one (which would create code duplication and complexity), let's address the fundamental question:
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.
> 💡 **What problems are we really trying to solve here?**
> What problems are we really trying to solve here?
[State management](https://en.wikipedia.org/wiki/State_management) focuses on solving two core challenges:
[State management](https://en.wikipedia.org/wiki/State_management) is all about finding a good approach to solve these two particular problems:
1. **Data Flow Clarity**: How do we keep track of where data comes from and where it goes?
2. **UI Synchronization**: How do we ensure the user interface always reflects the current state?
- 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)?
**Our Solution Strategy:**
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:
We'll implement a **centralized state management** approach that controls both the data and the methods that modify it:
![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 +107,74 @@ 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!
## Track Data Changes
With our centralized state structure in place, we can now implement one of the most powerful concepts in state management: controlled, immutable updates. This approach transforms unpredictable state changes into a reliable, debuggable system.
The key insight is that instead of allowing direct modifications to our state object, we'll channel all changes through a single, controlled function. This gives us complete visibility into what changes, when, and why.
**The Immutability Principle:**
We'll treat our `state` object as [*immutable*](https://en.wikipedia.org/wiki/Immutable_object), meaning it cannot be modified directly. Instead, we create a completely new state object whenever we need to make changes.
## Track data changes
**Benefits of immutable state management:**
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.
| 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 |
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.
**JavaScript Immutability with `Object.freeze()`:**
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 provides [`Object.freeze()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) to prevent object modifications:
✅ 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).
```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 +228,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
## Persist the State
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.
Now that we have centralized state management, we can tackle one of the most frustrating user experience issues: losing all data when the browser refreshes. Professional web applications remember user sessions and maintain state across browser restarts, and it's time to add this crucial functionality to our banking app.
When you want to persist data in your browser, there are a few important questions you should ask yourself:
State persistence bridges the gap between temporary in-memory data and long-term storage, creating a seamless user experience that feels native and reliable.
- *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
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.
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:**
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 +338,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
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');`.
**Testing Your Implementation:**
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...
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
## Refresh the data
🎉 **Achievement Unlocked**: You've successfully implemented persistent state management! Your app now behaves like a professional web application.
...But we might also have created a new one. Oops!
## Refresh the Data
Go to the dashboard using the `test` account, then run this command on a terminal to create a new transaction:
Our persistence system works perfectly, but we've inadvertently created a new challenge. While users stay logged in across sessions, the stored data can become outdated when changes occur on the server. This is a common issue in real-world applications where multiple devices or users might modify the same data.
Let's explore this problem and implement a solution that keeps our local state synchronized with the server.
**🧪 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 +389,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:**
Create a new function `updateAccountData`:
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.
### 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 +431,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 +448,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 +465,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 🚀
@ -270,9 +490,28 @@ Use the Agent mode to complete the following challenge:
## 🚀 Challenge
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?
Now that you have a robust data refresh system, it's time to optimize your storage strategy. Consider this important question: do you really need to persist *all* account data in localStorage?
**Your Challenge:**
Analyze what data is truly essential for maintaining user sessions and modify your persistence strategy accordingly.
**Think About These Questions:**
- What's the minimum data needed to keep a user logged in?
- Which data changes frequently and should always be fresh from the server?
- How can you balance storage efficiency with user experience?
**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
**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!

Loading…
Cancel
Save