moving folders

pull/1/head
Jen Looper 4 years ago
commit 21b1b67bf6

@ -0,0 +1,59 @@
# Web Development for Beginners - A Curriculum
Azure Cloud Advocates at Microsoft are pleased to offer a 12-week, 24-lesson curriculum all about JavaScript, CSS, and HTML basics. Each lesson includes pre- and post-lesson quizzes, written instructions to complete the lesson, a solution, an assignment and more. Our project-based pedagogy allows you to learn while building, a proven way for new skills to 'stick'.
[![Promo video](screenshot.png)](https://youtube.com/watch?v=kQyTzefCHxI "Promo video")
> Click the image above for a video about the project and the folks who created it!
## Pedagogy
We have chosen two pedagogical tenets while building this curriculum: ensuring that it is project-based and that it includes frequent quizzes. By the end of this series, students will have built a typing game, a virtual terrarium, a 'green' browser extension, a 'space invaders' type game, and a business-type banking app, and will have learned the basics of JavaScript, HTML, and CSS along with the modern toolchain of today's web developer.
By ensuring that the content aligns with projects, the process is made more engaging for students and retention of concepts will be augmented. We also wrote several starter lessons in JavaScript basics to introduce concepts, paired with video from the "[Beginners Series to: JavaScript](https://channel9.msdn.com/Series/Beginners-Series-to-JavaScript?WT.mc_id=github-webdev4beginners-cxa)" collection of video tutorials, some of whose authors contributed to this curriculum.
In addition, a low-stakes quiz before a class sets the intention of the student towards learning a topic, while a second quiz after class ensures further retention. This curriculum was designed to be flexible and fun and can be taken in whole or in part. The projects start small and become increasingly complex by the end of the 12 week cycle.
While we have purposefully avoided introducing JavaScript frameworks so as to concentrate on the basic skills needed as a web developer before adopting a framework, a good next step to completing this curriculum would be learning about Node.js via another collection of videos: "[Beginner Series to: Node.js](https://channel9.msdn.com/Series/Beginners-Series-to-Nodejs?WT.mc_id=github-webdev4beginners-cxa)".
## Each lesson includes:
- optional sketchnote
- optional supplemental video
- pre-lesson warmup quiz
- written lesson
- for project-based lessons, step-by-step guides on how to build the project
- knowledge checks
- a challenge
- supplemental reading
- assignment
- post-lesson quiz
## Lessons
| | Project Name | Concepts Taught | Learning Objectives | Linked Lesson | Written Lesson | Sketchnote | Assignment | Starting Quiz | Ending Quiz | Video | Author |
| :---: | :------------------------------------------------------------------------: | :------------------------------------------------------------------: | ----------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------: | :------------: | :--------: | :--------: | :-----------: | :---------: | :---: | :---------------------: |
| 01 | Getting Started | Introduction to Programming and Tools of the Trade | Learn the basic underpinnings behind most programming languages and about software that helps professional developers do their jobs | [Intro to Programming Languages and Tools of the Trade](../../../getting-started-lessons/tree/main/1-intro-to-programming-languages) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | Jasmine |
| 02 | Getting Started | Basics of GitHub, includes working with a team | How to use GitHub in your project, how to collaborate with others on a code base | [Intro to GitHub](../../../1-getting-started-lessons/tree/main/2-github-basics) | ✅ | ✅ | ✅ | ✅ | ✅ | 🛑 | Floor |
| 03 | Getting Started | Accessibility | Learn the basics of web accessibility | [Accessibility Fundamentals](../../../1-getting-started-lessons/tree/main/3-accessibility) | ✅ | ✅ | ✅ | ✅ | ✅ | 🛑 | Christopher |
| 04 | JS Basics | JavaScript Data Types | The basics of JavaScript data types | [Data Types](../../../2-js-basics/tree/main/1-data-types) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | Jasmine |
| 05 | JS Basics | Functions and Methods | Learn about functions and methods to manage an application's logic flow | [Functions and Methods](../../../2-js-basics/tree/main/2-functions-methods) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | Jasmine and Christopher |
| 06 | JS Basics | Making Decisions with JS | Learn how to create conditions in your code using decision-making methods | [Making Decisions](../../../2-js-basics/tree/main/3-making-decisions) | ✅ | | ✅ | ✅ | ✅ | ✅ | Jasmine |
| 07 | JS Basics | Arrays and Loops | Work with data using arrays and loops in JavaScript | [Arrays and Loops](../../../2-js-basics/tree/main/4-arrays-loops) | ✅ | | ✅ | ✅ | ✅ | ✅ | Jasmine |
| 08 | [Terrarium](../../../3-terrarium/tree/main/solution) | HTML in Practice | Build the HTML to create an online terrarium, focusing on building a layout | [Introduction to HTML](../../../3-terrarium/tree/main/1-intro-to-html) | ✅ | ✅ | ✅ | ✅ | ✅ | 🛑 | Jen |
| 09 | [Terrarium](../../../3-terrarium/tree/main/solution) | CSS in Practice | Build the CSS to style the online terrarium, focusing on the basics of CSS including making the page responsive | [Introduction to CSS](../../../3-terrarium/tree/main/2-intro-to-css) | ✅ | ✅ | ✅ | ✅ | ✅ | 🛑 | Jen |
| 10 | [Terrarium](../../../3-terrarium/tree/main/solution) | JavaScript Closures, DOM manipulation | Build the JavaScript to make the terrarium function as a drag/drop interface, focusing on closures and DOM manipulation | [JavaScript Closures, DOM manipulation](../../../3-terrarium/tree/main/3-intro-to-DOM-and-closures) | ✅ | ✅ | ✅ | ✅ | ✅ | 🛑 | Jen |
| 11 | [Typing Game](../../../4-typing-game/tree/main/solution) | Build a Typing Game | Learn how to use keyboard events to drive the logic of your JavaScript app | [Event-Driven Programming](../../../4-typing-game/tree/main/typing-game) | ✅ | 🛑 | ✅ | ✅ | ✅ | ✅ | Christopher |
| 12 | [Green Browser Extension](../../../5-browser-extension/tree/main/solution) | Working with Browsers | Learn how browsers work, their history, and how to scaffold the first elements of a browser extension | [About Browsers](../../../5-browser-extension/tree/main/1-about-browsers) | ✅ | ✅ | ✅ | ✅ | ✅ | 🛑 | Jen |
| 13 | [Green Browser Extension](../../../5-browser-extension/tree/main/solution) | Building a form, call and API and storing variables in local storage | Build the JavaScript elements of your browser extension to call an API using variables stored in local storage | [APIs, Forms, and Local Storage](../../../5-browser-extension/tree/main/2-forms-browsers-local-storage) | ✅ | 🛑 | ✅ | ✅ | ✅ | ✅ | Jen |
| 14 | [Green Browser Extension](../../../5-browser-extension/tree/main/solution) | Background processes in the browser, web performance | Use the browser's background processes to manage the extension's icon; learn about web performance and some optimizations to make | [Background Tasks and Performance](../../../5-browser-extension/tree/main/3-background-tasks-and-performance) | ✅ | 🛑 | ✅ | ✅ | ✅ | 🛑 | Jen |
| 15 | [Space Game](../../../6-space-game/tree/main/solution) | More Advanced Game Development with JavaScript | Learn about Inheritance using both Classes and Composition and the Pub/Sub pattern, in preparation for building a game | [Introduction to Advanced Game Development](../../../6-space-game/tree/main/1-introduction) | ✅ | 🛑 | ✅ | ✅ | ✅ | 🛑 | Chris |
| 16 | [Space Game](../../../6-space-game/tree/main/solution) | Drawing to canvas | Learn about the Canvas API, used to draw elements to a screen | [Drawing to Canvas](../../../6-space-game/tree/main/2-drawing-to-canvas) | ✅ | 🛑 | ✅ | ✅ | ✅ | 🛑 | Chris |
| 17 | [Space Game](../../../6-space-game/tree/main/solution) | Moving elements around the screen | Discover how elements can gain motion using the cartesian coordinates and the Canvas API | [Moving Elements Around](../../../6-space-game/tree/main/3-moving-elements-around) | ✅ | 🛑 | ✅ | ✅ | ✅ | 🛑 | Chris |
| 18 | [Space Game](../../../6-space-game/tree/main/solution) | Collision detection | Make elements collide and react to each other using keypresses and provide a cooldown function to ensure performance of the game | [Collision Detection](../../../6-space-game/tree/main/4-collision-detection) | ✅ | 🛑 | ✅ | ✅ | ✅ | 🛑 | Chris |
| 19 | [Space Game](../../../6-space-game/tree/main/solution) | Keeping score | Perform math calculations based on the game's status and performance | [Keeping Score](../../../6-space-game/tree/main/5-keeping-score) | ✅ | 🛑 | ✅ | ✅ | ✅ | 🛑 | Chris |
| 20 | [Space Game](../../../6-space-game/tree/main/solution) | Ending and restarting the game | Learn about ending and restarting the game, including cleaning up assets and resetting variable values | [The Ending Condition](../../../6-space-game/tree/main/6-end-condition) | ✅ | 🛑 | ✅ | ✅ | ✅ | 🛑 | Chris |
| 21 | [Banking App](../../../7-bank-project/tree/main/solution) | Layout and Route a Web Site | Learn how to create the scaffold of a multipage website's architecture using routing | [Layouts and Routes](../../../7-bank-project/tree/main/1-template-route) | ✅ | 🛑 | ✅ | ✅ | ✅ | ✅ | Yohan |
| 22 | [Banking App](../../../7-bank-project/tree/main/solution) | Build a Login and Registration Form | Learn about building forms and handing validation routines | [Forms](../../../7-bank-project/tree/main/2-forms) | ✅ | 🛑 | ✅ | ✅ | ✅ | ✅ | Yohan |
| 23 | [Banking App](../../../7-bank-project/tree/main/solution) | Methods of Fetching and Using Data | How data flows in and out of your app, how to fetch it, store it, and dispose of it | [Data](../../../7-bank-project/tree/main/3-data) | ✅ | 🛑 | ✅ | ✅ | ✅ | ✅ | Yohan |
| 24 | [Banking App](../../../7-bank-project/tree/main/solution) | Concepts of State Management | Learn how your app retains state and how to manage it programmatically | [State Management](../../../7-bank-project/tree/main/4-state-management) | | 🛑 | | | | | Yohan |

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

@ -0,0 +1,18 @@
*Complete this quiz by checking one answer per question.*
1. HTML templates are part of the DOM by default
- [ ] true
- [ ] false
2. Which part of the URL is needed for routing?
- [ ] window.location.pathname
- [ ] window.location.origin
- [ ] both
3. What's the name of the event triggered when calling the `history.pushState()` function?
- [ ] `pushstate`
- [ ] `popstate`
- [ ] `navigate`

@ -0,0 +1,19 @@
*A quick warm-up about web apps*
*Complete this quiz in class.*
1. You need to create multiple HTML files to display different screens in a web app
- [ ] true
- [ ] false
2. You can store and persist data locally in a web app
- [ ] true
- [ ] false
3. What's the best data provider for a web app?
- [ ] A local database
- [ ] A JavaScript object
- [ ] A server with a JSON API

@ -0,0 +1,293 @@
# HTML Templates and Routes in a Web App
<!-- ![video](video-url) -->
## [Pre-lecture quiz](.github/pre-lecture-quiz.md)
### Introduction
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.
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.
### Prerequisite
You need a local web server to test the web app we'll build in this lesson. If don't have one, you can install [Node.js](https://nodejs.org) and use the command `npx lite-server` from your project folder. It will create a local web server and open your app in a browser.
### Preparation
On your computer, create a folder named `bank` with a file named `index.html` inside it. We'll start from this HTML [boilerplate](https://en.wikipedia.org/wiki/Boilerplate_code):
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bank App</title>
</head>
<body>
<!-- This is where you'll work -->
</body>
</html>
```
---
## HTML templates
If you want to create multiples 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:
- You have to reload the entire HTML when switching screen, which can be slow.
- It's difficult to share data between the different screens.
Another approach is have only one HTML file, and define multiple [HTML templates](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template) using the `<template>` element. A template is a reusable HTML block that is not displayed by the browser, and needs to be instantiated at runtime using JavaScript.
### Task:
We'll create a bank app with two screens: the login page and the dashboard. First, let's add in the HTML body a placeholder element that we'll use to instantiate the different screens of our app:
```html
<div id="app">Loading...</div>
```
We're giving it an `id` to make it easier to locate it with JavaScript later.
> 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 button that we'll use to perform the navigation.
```html
<template id="login">
<h1>Bank App</h1>
<section>
<button>Login</button>
</section>
</template>
```
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 button
- The current balance of the bank account
- A list of transactions, displayed in a table
```html
<template id="dashboard">
<header>
<h1>Bank App</h1>
<button>Logout</button>
</header>
<section>
Balance: 100$
</section>
<section>
<h2>Transactions</h2>
<table>
<thead>
<tr>
<th>Date</th>
<th>Object</th>
<th>Amount</th>
</tr>
</thead>
<tbody></tbody>
</table>
</section>
</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 `<!-- -->`.
✅ Why do you think we use `id` attributes on the templates? Could we use something else like classes?
## Displaying templates with JavaScript
If you try your current HTML file in a browser, you'll see that it get stuck displaying `Loading...`. That's because we need to add some JavaScript code to instantiate and display the HTML templates.
Instantiating a template is usually done in 3 steps:
1. Retrieve the template element in the DOM, for example using [`document.getElementById`](https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementById).
2. Clone the template element, using [`cloneNode`](https://developer.mozilla.org/en-US/docs/Web/API/Node/cloneNode).
3. Attach it to the DOM under a visible element, for example using [`appendChild`](https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild).
✅ 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
Create a new file named `app.js` in your project folder and import that file in the `<head>` section of your HTML:
```html
<script src="app.js" defer></script>
```
Now in `app.js`, we'll create a new function `updateRoute`:
```js
function updateRoute(templateId) {
const template = document.getElementById(templateId);
const view = template.content.cloneNode(true);
const app = document.getElementById('app');
app.innerHTML = '';
app.appendChild(view);
}
```
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.
Now call this function with one of the template and look at the result.
```js
updateRoute('login');
```
✅ What's the purpose of this code `app.innerHTML = '';`? What happens without it?
## Creating routes
When talking about a web app, we call *Routing* the intent to map **URLs** to specific screens that should be displayed. On a web site 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:
```
mywebsite/index.html
mywebsite/login.html
mywebsite/admin/index.html
```
If you create a web server with `mywebsite` as the root, the URL mapping will be:
```
https://site.com --> mywebsite/index.html
https://site.com/login.html --> mywebsite/login.html
https://site.com/admin/ --> mywebsite/admin/index.html
```
However, for our web app we are using a single HTML file containing all the screens so this default behavior won't help us. We have to create this map manually and perform update the displayed template using JavaScript.
### Task
We'll use a simple object to implement a [map](https://en.wikipedia.org/wiki/Associative_array) between URL paths and our templates. Add this object at the top of your `app.js` file.
```js
const routes = {
'/login': { templateId: 'login' },
'/dashboard': { templateId: 'dashboard' },
};
```
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/en-US/docs/Web/API/Location/pathname) to get only the path section from the URL.
```js
function updateRoute() {
const path = window.location.pathname;
const route = routes[path];
const template = document.getElementById(route.templateId);
const view = template.content.cloneNode(true);
const app = document.getElementById('app');
app.innerHTML = '';
app.appendChild(view);
}
```
Here we mapped the routes we declared to the corresponding template. You can try it that it works correctlt 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
The next step for ou app is to add the possibility to navigate between pages without having to change the URL manually. This implies two things:
1. Updating the current URL
2. Updating the displayed template based on the new URL
We already took care of the second part with the `updateRoute` function, so we have to figure out how to update the current URL.
While the HTML anchor element [`<a>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a) can be used to create hyperlinks to different URLs, we can't use that here as it will make the browser reload the HTML.
Instead we'll have to use JavaScript and more specifically the [`history.pushState`](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState) that allows to update the URL and create a new entry in the browsing history, without reloading the HTML.
### Task
Let's create a new function we can use to navigate in our app:
```js
function navigate(path) {
window.history.pushState({}, path, window.location.origin + path);
updateRoute();
}
```
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.
```js
function updateRoute() {
const path = window.location.pathname;
const route = routes[path];
if (!route) {
return navigate('/login');
}
...
```
If a route cannot be found, we'll now redirect to the `login` page.
Let's complete the navigation system by adding bindings to our *Login* and *Logout* buttons in the HTML.
```html
<button onclick="navigate('/dashboard')">Login</button>
...
<button onclick="navigate('/login')">Logout</button>
```
Using the [`onclick`](https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onclick) attribute bind the `click` event to JavaScript code, here the call to the `navigate()` function.
Try clicking on these buttons, 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
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:
![Screenshot of navigation history](./history.png)
If you try clicking on the back button a few times, you'll see that the current URL changes and the history is updated, but the same template keeps being displayed.
That's because don't know that we need to call `updateRoute()` every time the history changes. If you take a look at the [`history.pushState` documentation](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState), you can see that if the state changes - meaning that we moved to a different URL - the [`popstate`](https://developer.mozilla.org/en-US/docs/Web/API/Window/popstate_event) event is triggered. We'll use that to fix that issue.
### Task
To make sure the displayed template is updated when the browser history changes, we'll attach a new function that calls `updateRoute()`. We'll do that at the bottom of our `app.js` file:
```js
window.onpopstate = () => updateRoute();
updateRoute();
```
> Note: we used an [arrow function](https://developer.mozilla.org/en-US/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:
[![Arrow Functions](https://img.youtube.com/vi/OP6eEbOj2sc/0.jpg)](https://youtube.com/watch?v=OP6eEbOj2sc "Arrow Functions")
Now try to use the back and forward buttons of your browsers, and check that the displayed is correctly updated this time.
---
## 🚀 Challenge
Add a new template and route for a third page that shows the credits for this app.
## [Post-lecture quiz](.github/post-lecture-quiz.md)
## Review & Self Study
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/en-us/azure/static-web-apps/routes) handles routing. Can you explain why some of the decisions described on that document are necessary?
## Assignment
[Improve the routing](assignment.md)

@ -0,0 +1,14 @@
# Improve the routing
## Instructions
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.
## Rubric
| Criteria | Exemplary | Adequate | Needs Improvement |
| -------- | ---------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
| | The two features are implemented and working. Title and code addition also work for a new route added in the `routes` declaration. | The two features work, but the behavior is hardcoded and not configurable via the `routes` declaration. Adding a third route with title and code addition does not work or works partially. | One of the features is missing or not working properly. |

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

@ -0,0 +1,287 @@
# Plantillas HTML y rutas en una aplicación web
<!-- ![video](video-url) -->
## [Pre-lecture prueba](.github/pre-lecture-quiz.md)
### Introducción
Desde la llegada de JavaScript a los navegadores, los sitios web se están volviendo más interactivos y complejos que nunca. Las tecnologías web ahora se utilizan comúnmente para crear aplicaciones completamente funcionales que se ejecutan directamente en un navegador que llamamos [aplicaciones web](https://en.wikipedia.org/wiki/Web_application). Como las aplicaciones web son muy interactivas, los usuarios no quieren esperar a que se vuelva a cargar la página completa cada vez que se realiza una acción. Es por eso que JavaScript se usa para actualizar el HTML directamente usando el DOM, para brindar una experiencia de usuario más fluida.
En esta lección, vamos a sentar las bases para crear una aplicación web bancaria, utilizando plantillas HTML para crear múltiples pantallas que se pueden mostrar y actualizar sin tener que volver a cargar toda la página HTML.
### Requisito previo
Necesita un servidor web local para probar la aplicación web que crearemos en esta lección. Si no tiene uno, puede instalar [Node.js](https://nodejs.org) y usar el comando `npx lite-server` de la carpeta de su proyecto. Creará un servidor web local y abrirá su aplicación en un navegador.
### Preparación
En su computadora, cree una carpeta llamada `bank` con un archivo llamado `index.html` dentro. Comenzaremos desde este HTML [boilerplate](https://en.wikipedia.org/wiki/Boilerplate_code):
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bank App</title>
</head>
<body>
<!-- Aquí es donde trabajarás -->
</body>
</html>
```
---
## Plantillas HTML
Si desea crear varias pantallas para una página web, una solución sería crear un archivo HTML para cada pantalla que desee mostrar. Sin embargo, esta solución tiene algunos inconvenientes:
- Tienes que volver a cargar todo el HTML al cambiar de pantalla, lo que puede ser lento.
- Es difícil compartir datos entre las diferentes pantallas.
Otro enfoque es tener un solo archivo HTML y definir múltiples [plantillas HTML](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template) usando el elemento `<template>`. Una plantilla es un bloque HTML reutilizable que el navegador no muestra y debe instanciarse en tiempo de ejecución mediante JavaScript.
### Tarea:
Crearemos una aplicación bancaria con dos pantallas: la página de inicio de sesión y el panel de control. Primero, agreguemos en el cuerpo HTML un elemento de marcador de posición que usaremos para instanciar las diferentes pantallas de nuestra aplicación:
```html
<div id="app">Loading...</div>
```
Le daremos un `id` para que sea más fácil localizarlo con JavaScript más adelante.
> Consejo: dado que el contenido de este elemento será reemplazado, podemos poner un mensaje de carga o un indicador que se mostrará mientras se carga la aplicación.
A continuación, agreguemos debajo la plantilla HTML para la página de inicio de sesión. Por ahora, solo pondremos allí un título y una sección que contiene un botón que usaremos para realizar la navegación.
```html
<template id="login">
<h1>Bank App</h1>
<section>
<button>Login</button>
</section>
</template>
```
Luego agregaremos otra plantilla HTML para la página del tablero. Esta página contendrá diferentes secciones:
- Un encabezado con un título y un botón para cerrar sesión
- El saldo actual de la cuenta bancaria
- Una lista de transacciones, que se muestra en una tabla.
```html
<template id="dashboard">
<header>
<h1>Bank App</h1>
<button>Logout</button>
</header>
<section>
Balance: 100$
</section>
<section>
<h2>Transactions</h2>
<table>
<thead>
<tr>
<th>Date</th>
<th>Object</th>
<th>Amount</th>
</tr>
</thead>
<tbody></tbody>
</table>
</section>
</template>
```
Sugerencia: al crear plantillas HTML, si desea ver cómo se verá, puede comentar las líneas `<template>` y `</template>` encerrándolas entre `<!-- -->`.
✅ ¿Por qué crees que usamos atributos ʻid` en las plantillas? ¿Podríamos usar algo más como clases?
## Visualización de plantillas con JavaScript
Si prueba su archivo HTML actual en un navegador, verá que se atasca mostrando `Loading...`. Eso es porque necesitamos agregar código JavaScript para crear instancias y mostrar las plantillas HTML.
La instanciación de una plantilla se suele realizar en 3 pasos:
1. Recupere el elemento de plantilla en el DOM, por ejemplo, usando [`document.getElementById`](https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementById).
2. Clone el elemento de plantilla, usando [`cloneNode`](https://developer.mozilla.org/en-US/docs/Web/API/Node/cloneNode).
3. Adjúntelo al DOM bajo un elemento visible, por ejemplo usando [ʻappendChild`](https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild).
✅ ¿Por qué necesitamos clonar la plantilla antes de adjuntarla al DOM? ¿Qué crees que pasaría si nos salteáramos este paso?
### Tarea
Cree un nuevo archivo llamado `app.js` en la carpeta de su proyecto e importe ese archivo en la sección `<head>` de su HTML:
```html
<script src="app.js" defer></script>
```
Ahora en `app.js`, crearemos una nueva función `updateRoute`:
```js
function updateRoute(templateId) {
const template = document.getElementById(templateId);
const view = template.content.cloneNode(true);
const app = document.getElementById('app');
app.innerHTML = '';
app.appendChild(view);
}
```
Lo que hacemos aquí son exactamente los 3 pasos descritos anteriormente. Instalamos la plantilla con el id `templateId` y colocamos su contenido clonado dentro de nuestro marcador de posición de la aplicación. Tenga en cuenta que necesitamos usar `cloneNode (true)` para copiar todo el subárbol de la plantilla.
Ahora llame a esta función con una de las plantillas y observe el resultado.
```js
updateRoute('login');
```
✅ ¿Cuál es el propósito de este código `app.innerHTML = '';`? ¿Qué pasa sin él?
## Creando rutas
Cuando hablamos de una aplicación web, llamamos *Enrutamiento* la intención de asignar **URL** a pantallas específicas que deben mostrarse. En un sitio web con varios archivos HTML, esto se hace automáticamente ya que las rutas de los archivos se reflejan en la URL. Por ejemplo, con estos archivos en la carpeta de su proyecto:
```
mywebsite/index.html
mywebsite/login.html
mywebsite/admin/index.html
```
Si crea un servidor web con `mywebsite` como raíz, la asignación de URL será:
```
https://site.com --> mywebsite/index.html
https://site.com/login.html --> mywebsite/login.html
https://site.com/admin/ --> mywebsite/admin/index.html
```
Sin embargo, para nuestra aplicación web estamos usando un solo archivo HTML que contiene todas las pantallas, por lo que este comportamiento predeterminado no nos ayudará. Tenemos que crear este mapa manualmente y actualizar la plantilla mostrada usando JavaScript.
### Tarea
Usaremos un objeto simple para implementar un [mapa] (https://en.wikipedia.org/wiki/Associative_array) entre las rutas de URL y nuestras plantillas. Agregue este objeto en la parte superior de su archivo `app.js`.
```js
const routes = {
'/login': { templateId: 'login' },
'/dashboard': { templateId: 'dashboard' },
};
```
Ahora modifiquemos un poco la función `updateRoute`. En lugar de pasar directamente el `templateId` como argumento, queremos recuperarlo mirando primero la URL actual y luego usar nuestro mapa para obtener el valor de ID de plantilla correspondiente. Podemos usar [`window.location.pathname`](https://developer.mozilla.org/en-US/docs/Web/API/Location/pathname) para obtener solo la sección de ruta de la URL.
```js
function updateRoute() {
const path = window.location.pathname;
const route = routes[path];
const template = document.getElementById(route.templateId);
const view = template.content.cloneNode(true);
const app = document.getElementById('app');
app.innerHTML = '';
app.appendChild(view);
}
```
Aquí mapeamos las rutas que declaramos a la plantilla correspondiente. Puede probar que funciona correctamente cambiando la URL manualmente en su navegador.
✅ ¿Qué sucede si ingresa una ruta desconocida en la URL? ¿Cómo podríamos solucionar esto?
## Añadiendo navegación
El siguiente paso para nuestra aplicación es agregar la posibilidad de navegar entre páginas sin tener que cambiar la URL manualmente. Esto implica dos cosas:
1. Actualización de la URL actual
2. Actualización de la plantilla mostrada en función de la nueva URL
Ya nos ocupamos de la segunda parte con la función `updateRoute`, así que tenemos que averiguar cómo actualizar la URL actual.
Si bien el elemento de anclaje HTML [`<a>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a) se puede utilizar para crear hipervínculos a diferentes URL, podemos ' No use eso aquí, ya que hará que el navegador vuelva a cargar el HTML.
En su lugar, tendremos que usar JavaScript y más específicamente el [`history.pushState`](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState) que permite actualizar la URL y cree una nueva entrada en el historial de navegación, sin volver a cargar el HTML.
### Tarea
Creemos una nueva función que podamos usar para navegar en nuestra aplicación:
```js
function navigate(path) {
window.history.pushState({}, path, window.location.origin + path);
updateRoute();
}
```
Este método primero actualiza la URL actual según la ruta proporcionada y luego actualiza la plantilla. La propiedad `window.location.origin` devuelve la raíz de la URL, lo que nos permite reconstruir una URL completa a partir de una ruta determinada.
Ahora que tenemos esta función, podemos solucionar el problema que tenemos si una ruta no coincide con ninguna ruta definida. Modificaremos la función `updateRoute` agregando un respaldo a una de las rutas existentes si no podemos encontrar una coincidencia.
```js
function updateRoute() {
const path = window.location.pathname;
const route = routes[path];
if (!route) {
return navigate('/login');
}
...
```
Si no se puede encontrar una ruta, ahora lo redireccionaremos a la página de `login`.
Completemos el sistema de navegación agregando enlaces a nuestros botones *login* y *logout* en el HTML.
```html
<button onclick="navigate('/dashboard')">Login</button>
...
<button onclick="navigate('/login')">Logout</button>
```
Usando el atributo [`onclick`] (https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onclick) enlaza el evento `click` al código JavaScript, aquí la llamada al` navigate()`función.
Intente hacer clic en estos botones, ahora debería poder navegar entre las diferentes pantallas de su aplicación.
✅ El método `history.pushState` es parte del estándar HTML5 y está implementado en [todos los navegadores modernos](https://caniuse.com/?search=pushState). Si está creando una aplicación web para navegadores más antiguos, hay un truco que puede usar en lugar de esta API: usar un [hash (`#`)](https://en.wikipedia.org/wiki/URI_fragment) antes la ruta puede implementar un enrutamiento que funcione con la navegación de anclaje regular y no recargue la página, ya que su propósito era crear enlaces internos dentro de una página.
## Manejo de los botones de avance y retroceso del navegador
El uso de `history.pushState` crea nuevas entradas en el historial de navegación del navegador. Puede verificar que manteniendo presionado el *botón de retroceso* de su navegador, debería mostrar algo como esto:
![Captura de pantalla del historial de navegación](./history.png)
Si intenta hacer clic en el botón Atrás varias veces, verá que la URL actual cambia y el historial se actualiza, pero se sigue mostrando la misma plantilla.
Eso es porque no sabemos que necesitamos llamar a `updateRoute()` cada vez que cambia el historial. Si echas un vistazo a la [`history.pushState` documentation](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState), puedes ver que si el estado cambia - lo que significa que nos mudamos a una URL diferente - se activa el evento [`popstate`](https://developer.mozilla.org/en-US/docs/Web/API/Window/popstate_event). Lo usaremos para solucionar ese problema.
### Tarea
Para asegurarnos de que la plantilla mostrada se actualice cuando cambie el historial del navegador, adjuntaremos una nueva función que llama a `updateRoute()`. Lo haremos en la parte inferior de nuestro archivo `app.js`:
```js
window.onpopstate = () => updateRoute();
updateRoute();
```
> Nota: utilizamos una [función de flecha](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) aquí para declarar nuestro controlador de eventos `popstate` por concisión, pero un la función regular funcionaría igual.
Ahora intente utilizar los botones de avance y retroceso de sus navegadores y compruebe que esta vez lo que se muestra está actualizado correctamente.
---
## 🚀 Desafío
Agregue una nueva plantilla y ruta para una tercera página que muestre los créditos de esta aplicación.
## [Post-lecture prueba](.github/post-lecture-quiz.md)
## Revisión y autoestudio
**Tarea**: [Mejorar el enrutamiento](assignment.md)

@ -0,0 +1,14 @@
# Mejorar el enrutamiento
## Instrucciones
La declaración de rutas contiene actualmente solo el ID de plantilla a usar. Pero cuando se muestra una página nueva, a veces se necesita un poco más. Mejoremos nuestra implementación de enrutamiento con dos características adicionales:
- Dé títulos a cada plantilla y actualice el título de la ventana con él cuando cambie la plantilla.
- Agregue una opción para ejecutar código después de que cambie la plantilla. Queremos imprimir `'Se muestra el panel'` en la consola del desarrollador cada vez que se muestra la página del panel.
## Rúbrica
| Criterios | Ejemplar | Adecuado | Necesita mejorar |
| -------- | --------- | -------- | ----------------- |
| | Las dos características están implementadas y funcionando. La adición de título y código también funciona para una nueva ruta agregada en la declaración de `routes`. | Las dos características funcionan, pero el comportamiento está codificado y no se puede configurar a través de la declaración de `routes`. Agregar una tercera ruta con la adición de título y código no funciona o funciona parcialmente. | Una de las funciones falta o no funciona correctamente.

@ -0,0 +1,17 @@
*Complete this quiz by checking one answer per question.*
1. Using `<label>` elements in forms is only for making the form pretty
- [ ] true
- [ ] false
2. How can you define how a form is sent to the server?
- [ ] using the `action` attribute
- [ ] using the `method` attribute
- [ ] both
3. Which attribute can you use to set the maximum size of a text `<input>`?
- [ ] `max`
- [ ] `maxlength`
- [ ] `pattern`

@ -0,0 +1,16 @@
*Complete this quiz in class.*
1. HTML forms allow to send user input to a server without using JavaScript
- [ ] true
- [ ] false
2. `<label>` elements are mandatory for every form control
- [ ] true
- [ ] false
3. It is secure to send form data to a server over HTTP
- [ ] true
- [ ] false

@ -0,0 +1,286 @@
# Build a Login and Registration Form
## [Pre-lecture quiz](.github/pre-lecture-quiz.md)
### Introduction
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 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.
### Prerequisite
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.
You can test that the server is running properly by executing this command in a terminal:
```sh
curl http://localhost:5000/api
# -> should return "Bank API v1.0.0" as a result
```
---
## Form and controls
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.
There are a lot of different [types](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input) of `<input>`, for example to create a field where the user can enter its username you can use:
```html
<input name="username" type="text">
```
The `name` attribute is used to identify the control and will be used as the property name when the form data will be sent over.
> Take a look at the whole list of [`<input>` types](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input) and [other form controls](https://developer.mozilla.org/en-US/docs/Learn/Forms/Other_form_controls) to get an idea of all the native UI elements you can use when building your UI.
✅ Note that `<input>` is an [empty element](https://developer.mozilla.org/en-US/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.
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:
- `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.
### Task:
Let's start by adding a form to the `login` template. We'll need a *username* field and a *Login* button.
```html
<template id="login">
<h1>Bank App</h1>
<section>
<h2>Login</h2>
<form id="loginForm">
<label for="user">Username</label>
<input name="user" type="text">
<button>Login</button>
</form>
</section>
</template>
```
If you take a closer look, you can notice that we also added a `<label>` element here. `<label>` are used to add a caption for UI controls, such as our username field. Labels are important for the readbility of your forms, but also comes with additional benefits:
- 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](https://developer.mozilla.org/en-US/docs/Learn/Accessibility/What_is_accessibility) on the web is a very important topic that's often overlooked. Thanks to [HTML5 semantic elements](https://developer.mozilla.org/en-US/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/en-US/docs/Web/Accessibility) to avoid common mistakes and become a responsible developer.
Now we'll add a second form for the registration, just below the previous one:
```html
<hr/>
<h2>Register</h2>
<form id="registerForm">
<label for="user">Username</label>
<input name="user" type="text">
<label for="currency">Currency</label>
<input name="currency" type="text" value="$">
<label for="description">Description</label>
<input name="description" type="text">
<label for="balance">Current balance</label>
<input name="balance" type="number" value="0">
<button>Register</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.
✅ Can you navigate and interact with the forms using only a keyboard? How would you do that?
## Submitting data to the server
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?
Did you notice the change in your browser's URL section?
![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:
- 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
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.
> 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.
### Task
Add `action` and `method` properties to the registration form:
```html
<form id="registerForm" action="//localhost:5000/api/accounts" method="POST">
```
Now try to register a new account with your name. After clicking on the *Register* button you should see something like this:
![](./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.
✅ Try registering again with the same name. What happens?
## Submitting data without reloading the page
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 makng a [Single-page application (SPA)](https://en.wikipedia.org/wiki/Single-page_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:
- Retrieve the form data
- Convert and encode the form data to a suitable format
- Create the HTTP request and send it to the server
### Task
Replace the registration form `action` with:
```html
<form id="registerForm" action="javascript:register()">
```
Open `app.js` add a new function named `register`:
```js
function register() {
const registerForm = document.getElementById('registerForm');
const formData = new FormData(registerForm);
const data = Object.fromEntries(formData);
const jsonData = JSON.stringify(data);
}
```
Here we retrieve the form element using `getElementById()` and use the [`FormData`](https://developer.mozilla.org/en-US/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/en-US/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.
The data is now ready to be sent to the server. Create a new function named `createAccount`:
```js
async function createAccount(account) {
try {
const response = await fetch('//localhost:5000/api/accounts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: account
});
return await response.json();
} catch (error) {
return { error: error.message || 'Unknown error' };
}
}
```
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/en-US/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")
We use the `fetch()` API to send JSON data to the server. This method takes 2 parameters:
- 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 know how to interpret the content.
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.
Now add some code to the `register` function to call `createAccount()`:
```js
const result = await createAccount(jsonData);
```
Because we use the `await` keyword here, we need to add the `async` keyword before the register function:
```js
async function register() {
```
Finally, let's add some logs to check the result. The final function should look like this:
```js
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 occured:', result.error);
}
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/en-US/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.
![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.
## Data validation
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/en-US/docs/Web/HTTP/Status/400#:~:text=The%20HyperText%20Transfer%20Protocol%20(HTTP,%2C%20or%20deceptive%20request%20routing).).
Before sending data to a server it's a good practice to [validate the form data](https://developer.mozilla.org/en-US/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:
- `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/en-US/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/en-US/docs/Web/JavaScript/Guide/Regular_Expressions) pattern to test if the entered data is valid or not.
> 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.
### Task
There are 2 required fields to create a valid new account, the username and currency, the other fields being optional. Update the form in the HTML to reflect that:
```html
<input name="user" type="text" required>
...
<input name="currency" type="text" value="$" required>
```
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.
Add a `maxlength` attribute to the text fields:
```html
<input name="user" type="text" maxlength="20" required>
...
<input name="currency" type="text" value="$" maxlength="5" required>
...
<input name="description" type="text" maxlength="100">
```
Now if you press the *Register* button and a field does not respect a validation rule we defined, you should see something like this:
![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's it's not always possible to peform 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.
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.
---
## 🚀 Challenge
Show an error message in the HTML if the user already exists.
Here's an example of what the final login page can look like after a bit of styling:
![Screenshot of the login page after adding CSS styles](./images/result.png)
## [Post-lecture quiz](.github/post-lecture-quiz.md)
## Review & Self Study
Developers have gotten very creative about their form building efforts, especially regarding validation strategies. Learn about different form flows by looking through [CodePen](https://codepen.com); can you find some interesting and inspiring forms?
## Assignment
[Style your bank app](assignment.md)

@ -0,0 +1,13 @@
# Style your bank app
## 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.
> Tip: you can modify the HTML and add new elements and classes if needed.
## Rubric
| 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. |

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

@ -0,0 +1,291 @@
# Cree un formulario de inicio de sesión y registro
## [Prueba previa a la conferencia](.github/prelecture-quiz.md)
### Introducción
En casi todas las aplicaciones web modernas, puede crear una cuenta para tener su propio espacio privado. Como varios usuarios pueden acceder a una aplicación web al mismo tiempo, necesita un mecanismo para almacenar los datos personales de cada usuario por separado y seleccionar qué información mostrar. No cubriremos cómo administrar [la identidad del usuario de forma segura](https://en.wikipedia.org/wiki/Authentication) ya que es un tema extenso en sí mismo, pero nos aseguraremos de que cada usuario pueda crear uno. O más cuenta bancaria en nuestra aplicación.
En esta parte usaremos formularios HTML para agregar inicio de sesión y registro a nuestra aplicación web. Veremos cómo enviar los datos a una API de servidor de forma programática y, en última instancia, cómo definir reglas de validación básicas para las entradas del usuario.
### Requisito previo
Debe haber completado las [plantillas HTML y enrutamiento](../1-template-route/README.md) de la aplicación web para esta lección. También necesita instalar [Node.js](https://nodejs.org) y [ejecutar la API del servidor](../api/README.md) localmente para poder enviar datos para crear cuentas.
Puede probar que el servidor está funcionando correctamente ejecutando este comando en una terminal:
```sh
curl http://localhost:5000/api
# -> should return "Bank API v1.0.0" as a result
```
---
## Forma y controles
El elemento `<form>` encapsula una sección de un documento HTML donde el usuario puede ingresar y enviar datos con controles interactivos. Hay todo tipo de controles de interfaz de usuario (UI) que se pueden usar dentro de un formulario, siendo el más común los elementos `<input>` y `<button>`.
Hay muchos [types diferentes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input) de `<input>`, por ejemplo, para crear un campo donde el usuario puede ingresar su nombre de usuario que puede usar:
```html
<input name="username" type="text">
```
El atributo `name` se usa para identificar el control y se usará como el nombre de la propiedad cuando se envíen los datos del formulario.
> Eche un vistazo a la lista completa de [`<input>` tipos](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input) y [otros controles de formulario](https://developer.mozilla.org/en-US/docs/Learn/Forms/Other_form_controls) para tener una idea de todos los elementos nativos de la interfaz de usuario que puede utilizar al crear su interfaz de usuario.
✅ Tenga en cuenta que `<input>` es un [elemento vacío](https://developer.mozilla.org/en-US/docs/Glossary/Empty_element) en el que *no* debe agregar una etiqueta de cierre coincidente. Sin embargo, puede usar la notación de cierre automático `<input/>`, pero no es necesaria.
El elemento `<button>` dentro de un formulario es un poco especial. Si no especifica su atributo `type`, automáticamente enviará los datos del formulario al servidor cuando se presione. Estos son los posibles valores de tipo:
- `enviar`: el valor predeterminado dentro de un` <formulario> `, el botón activa la acción de envío del formulario.
- `reset`: El botón restablece todos los controles de formulario a sus valores iniciales.
- `button`: No asigna un comportamiento predeterminado cuando se presiona el botón. A continuación, puede asignarle acciones personalizadas mediante JavaScript.
### Tarea:
Comencemos agregando un formulario a la plantilla de inicio de sesión. Necesitaremos un campo *nombre de usuario* y un botón *Iniciar sesión*.
```html
<template id="login">
<h1>Bank App</h1>
<section>
<h2>Login</h2>
<form id="loginForm">
<label for="user">Username</label>
<input name="user" type="text">
<button>Login</button>
</form>
</section>
</template>
```
Si observa más de cerca, puede notar que también agregamos un elemento `<label>` aquí. Las `<label>` se utilizan para agregar un título para los controles de la IU, como nuestro campo de nombre de usuario. Las etiquetas son importantes para la legibilidad de sus formularios, pero también tienen beneficios adicionales:
- Al asociar una etiqueta a un control de formulario, ayuda a los usuarios que utilizan tecnologías de asistencia (como un lector de pantalla) a comprender qué datos se espera que proporcionen.
- Puede hacer clic en la etiqueta para centrarse directamente en la entrada asociada, lo que facilita el acceso a los dispositivos con pantalla táctil.
> [Accesibilidad](https://developer.mozilla.org/en-US/docs/Learn/Accessibility/What_is_accessibility) en la web es un tema muy importante que a menudo se pasa por alto. Gracias a los [elementos semánticos HTML5](https://developer.mozilla.org/en-US/docs/Learn/Accessibility/HTML) no es difícil crear contenido accesible si los usas correctamente. Puede [leer más sobre accesibilidad](https://developer.mozilla.org/en-US/docs/Web/Accessibility) para evitar errores comunes y convertirse en un desarrollador responsable.
Ahora agregaremos un segundo formulario para el registro, justo debajo del anterior:
```html
<hr/>
<h2>Register</h2>
<form id="registerForm">
<label for="user">Username</label>
<input name="user" type="text">
<label for="currency">Currency</label>
<input name="currency" type="text" value="$">
<label for="description">Description</label>
<input name="description" type="text">
<label for="balance">Current balance</label>
<input name="balance" type="number" value="0">
<button>Register</button>
</form>
```
Usando el atributo `value` podemos definir un valor predeterminado para una entrada dada.
Observe también que la entrada para `balance` tiene el tipo `number`. ¿Se ve diferente a las otras entradas? Intenta interactuar con él.
✅ ¿Puede navegar e interactuar con los formularios usando solo un teclado? ¿Cómo lo harías tú?
## Envío de datos al servidor
Ahora que tenemos una interfaz de usuario funcional, el siguiente paso es enviar los datos a nuestro servidor. Hagamos una prueba rápida con nuestro código actual: ¿qué sucede si hace clic en el botón *Iniciar sesión* o *Registrarse*?
¿Notó el cambio en la sección de URL de su navegador?
! [Captura de pantalla del cambio de URL del navegador después de hacer clic en el botón Registrar](./images/click-register.png)
La acción predeterminada para un `<form>` es enviar el formulario a la URL del servidor actual utilizando el [método GET](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3 ), agregando los datos del formulario directamente a la URL. Sin embargo, este método tiene algunas deficiencias:
- Los datos enviados son de tamaño muy limitado (unos 2000 caracteres)
- Los datos son directamente visibles en la URL (no es ideal para contraseñas)
- No funciona con cargas de archivos.
Es por eso que puede cambiarlo para usar el [método POST](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5) que envía los datos del formulario al servidor en el cuerpo de la solicitud HTTP, sin ninguna de las limitaciones anteriores.
> Si bien POST es el método más utilizado para enviar datos, [en algunos escenarios específicos](https://www.w3.org/2001/tag/doc/whenToUseGet.html) es preferible utilizar el método GET, al implementar un campo de búsqueda, por ejemplo.
### Tarea
Agregue las propiedades `action` y `method` al formulario de registro:
```html
<form id="registerForm" action="//localhost:5000/api/accounts" method="POST">
```
Ahora intente registrar una nueva cuenta con su nombre. Después de hacer clic en el botón * Registrarse *, debería ver algo como esto:
![](./images/form-post.png)
Si todo va bien, el servidor debe responder a su solicitud con una respuesta [JSON](https://www.json.org/json-en.html) que contenga los datos de la cuenta que se creó.
✅ Intente registrarse nuevamente con el mismo nombre. ¿Lo que pasa?
## Envío de datos sin recargar la página
Como probablemente haya notado, hay un pequeño problema con el enfoque que acabamos de usar: al enviar el formulario, salimos de nuestra aplicación y el navegador redirige a la URL del servidor. Estamos tratando de evitar todas las recargas de páginas con nuestra aplicación web, ya que estamos creando una [Aplicación de una sola página (SPA)](https://en.wikipedia.org/wiki/Single-page_application).
Para enviar los datos del formulario al servidor sin forzar la recarga de una página, tenemos que usar código JavaScript. En lugar de poner una URL en la propiedad `action` de un elemento `<form>`, puede usar cualquier código JavaScript precedido por la cadena `javascript:` para realizar una acción personalizada. Usar esto también significa que tendrá que implementar algunas tareas que anteriormente el navegador realizaba automáticamente:
- Recuperar los datos del formulario
- Convierta y codifique los datos del formulario a un formato adecuado
- Crea la solicitud HTTP y envíala al servidor
### Tarea
Reemplace el formulario de registro `acción` con:
```html
<form id="registerForm" action="javascript:register()">
```
Abra `app.js` agregue una nueva función llamada `registro`:
```js
function register() {
const registerForm = document.getElementById('registerForm');
const formData = new FormData(registerForm);
const data = Object.fromEntries(formData);
const jsonData = JSON.stringify(data);
}
```
Aquí recuperamos el elemento del formulario usando `getElementById()` y usamos el ayudante [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) para extraer los valores del formulario controles como un conjunto de pares clave/valor. Luego convertimos los datos a un objeto regular usando [`Object.fromEntries()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/fromEntries) y finalmente serialice los datos en [JSON](https://www.json.org/json-en.html), un formato que se utiliza comúnmente para intercambiar datos en la web.
Los datos ahora están listos para enviarse al servidor. Cree una nueva función llamada `createAccount`:
```js
async function createAccount(account) {
try {
const response = await fetch('//localhost:5000/api/accounts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: account
});
return await response.json();
} catch (error) {
return { error: error.message || 'Unknown error' };
}
}
```
¿Qué hace esta función? Primero, observe la palabra clave `async` aquí. Esto significa que la función contiene código que se ejecutará [** asincrónicamente**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function). Cuando se usa junto con la palabra clave `await`, permite esperar a que se ejecute el código asincrónico, como esperar la respuesta del servidor aquí, antes de continuar.
Aquí hay un video rápido sobre el uso de `async/await`:
[![Async y Await para administrar promesas](https://img.youtube.com/vi/YwmlRkrxvkk/0.jpg)] (https://youtube.com/watch?v=YwmlRkrxvkk "Async y Await para administrar promesas")
Usamos la API `fetch()` para enviar datos JSON al servidor. Este método toma 2 parámetros:
- La URL del servidor, por lo que volvemos a colocar `//localhost:5000/api/accounts` aquí.
- La configuración de la solicitud. Ahí es donde establecemos el método en `POST` y proporcionamos el `body` de la solicitud. Como estamos enviando datos JSON al servidor, también necesitamos establecer el encabezado `Content-Type` en `application/json` para que el servidor sepa cómo interpretar el contenido.
Como el servidor responderá a la solicitud con JSON, podemos usar `await response.json()` para analizar el contenido JSON y devolver el objeto resultante. Tenga en cuenta que este método es asíncrono, por lo que usamos la palabra clave `await` aquí antes de regresar para asegurarnos de que también se detecta cualquier error durante el análisis.
Ahora agregue un código a la función `register` para llamar a `createAccount()`:
```js
const result = await createAccount(jsonData);
```
Debido a que usamos la palabra clave `await` aquí, necesitamos agregar la palabra clave `async` antes de la función de registro:
```js
async function register() {
```
Finalmente, agreguemos algunos registros para verificar el resultado. La función final debería verse así:
```js
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 occured:', result.error);
}
console.log('Account created!', result);
}
```
¡Eso fue un poco largo pero llegamos allí! Si abre sus [herramientas de desarrollo del navegador](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/What_are_browser_developer_tools) e intenta registrar una nueva cuenta, no debería ver ningún cambio en la página web pero aparecerá un mensaje en la consola confirmando que todo funciona.
![Captura de pantalla que muestra el mensaje de registro en la consola del navegador](./images/browser-console.png)
✅ ¿Crees que los datos se envían al servidor de forma segura? ¿Y si alguien pudiera interceptar la solicitud? Puede leer sobre [HTTPS](https://en.wikipedia.org/wiki/HTTPS) para saber más sobre la comunicación segura de datos.
## Validación de datos
Si intenta registrar una nueva cuenta sin establecer un nombre de usuario primero, puede ver que el servidor devuelve un error con el código de estado [400 (Solicitud incorrecta)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400#:~:text=The%20HyperText%20Transfer%20Protocol%20(HTTP,%2C%20or%20deceptive%20request%20routing).).
Antes de enviar datos a un servidor, es una buena práctica [validar los datos del formulario](https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation) de antemano cuando sea posible, para asegurarse de enviar un solicitud válida. Los controles de formularios HTML5 proporcionan una validación incorporada utilizando varios atributos:
- `required`: el campo debe completarse; de lo contrario, el formulario no se podrá enviar
- `minlength` y `maxlength`: define el número mínimo y máximo de caracteres en los campos de texto.
- `min` y `max`: define el valor mínimo y máximo de un campo numérico.
- `type`: define el tipo de datos esperados, como `number`, `email`, `file` u [otros tipos integrados](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input). Este atributo también puede cambiar la representación visual del control de formulario.
- `patrón`: permite definir un patrón [expresión regular](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions) para probar si los datos ingresados son válidos o no.
> Consejo: puedes personalizar el aspecto de los controles de tu formulario dependiendo de si son válidos o no usando las pseudoclases CSS `:valid` y `:invalid`.
### Tarea
Hay 2 campos obligatorios para crear una nueva cuenta válida, el nombre de usuario y la moneda, los otros campos son opcionales. Actualice el formulario en HTML para reflejar que:
```html
<input name="user" type="text" required>
...
<input name="currency" type="text" value="$" required>
```
Si bien esta implementación de servidor en particular no impone límites específicos en la longitud máxima de los campos, siempre es una buena práctica definir límites razonables para cualquier entrada de texto del usuario.
Agrega un atributo `maxlength` a los campos de texto:
```html
<input name="user" type="text" maxlength="20" required>
...
<input name="currency" type="text" value="$" maxlength="5" required>
...
<input name="description" type="text" maxlength="100">
```
Ahora, si presiona el botón *Registrar* y un campo no respeta una regla de validación que definimos, debería ver algo como esto:
![Captura de pantalla que muestra el error de validación al intentar enviar el formulario](./images/validation-error.png)
Una validación como esta realizada *antes* de enviar cualquier dato al servidor se llama validación **del lado del cliente**. Pero tenga en cuenta que no siempre es posible realizar todas las comprobaciones sin enviar los datos. Por ejemplo, no podemos comprobar aquí si ya existe una cuenta con el mismo nombre de usuario sin enviar una solicitud al servidor. La validación adicional realizada en el servidor se denomina validación **del lado del servidor**.
Por lo general, ambos deben implementarse y, si bien el uso de la validación del lado del cliente mejora la experiencia del usuario al proporcionar comentarios instantáneos al usuario, la validación del lado del servidor es crucial para garantizar que los datos del usuario que manipula sean sólidos y seguros.
---
## 🚀 Desafío
Muestra un mensaje de error en el HTML si el usuario ya existe.
Aquí hay un ejemplo de cómo puede verse la página de inicio de sesión final después de un poco de estilo:
![Captura de pantalla de la página de inicio de sesión después de agregar estilos CSS](./images/result.png)
## [Prueba posterior a la conferencia](.github/post-lecture-quiz.md)
## Revisión y autoestudio
Los desarrolladores se han vuelto muy creativos en sus esfuerzos de creación de formularios, especialmente en lo que respecta a las estrategias de validación. Obtenga información sobre los diferentes flujos de formularios consultando [CodePen](https://codepen.com); ¿Puedes encontrar algunas formas interesantes e inspiradoras?
## Asignación
[Diseña tu aplicación bancaria](assignment.md)

@ -0,0 +1,13 @@
# Diseña tu aplicación bancaria
## Instrucciones
Cree un nuevo archivo `styles.css` y agréguele un enlace en su archivo `index.html` actual. En el archivo CSS que acaba de crear, agregue algunos estilos para que la página *Inicio de sesión* y *Panel de control* se vea bien y ordenada. Intente crear un tema de color para darle a su aplicación su propia marca.
> Consejo: puede modificar el HTML y agregar nuevos elementos y clases si es necesario.
## Rúbrica
| Criterios | Ejemplar | Adecuado | Necesita mejorar |
| -------- | -------------------------------------------------- -------------------------------------------------- ------------------- | -------------------------------------------------- ---------------------------- | -------------------------------------------------- ------------------------------------------- |
| | Todas las páginas se ven limpias y legibles, con un tema de color consistente y las diferentes secciones destacando correctamente. | Las páginas tienen un estilo pero sin un tema o con secciones no claramente delimitadas. | Las páginas carecen de estilo, las secciones se ven desorganizadas y la información es difícil de leer. |

@ -0,0 +1,17 @@
*Complete this quiz by checking one answer per question.*
1. In a Single-page application, the HTML is loaded once and never updated:
- [ ] true
- [ ] false
2. Why is it important to not trust data coming from user input?
- [ ] because using special characters can make the UI ugly.
- [ ] because it can contain non-sense or offensive words.
- [ ] because it can be used as vector of attack to execute malicious scripts.
3. What's the API name for sending asynchronous HTTP requests to a web server?
- [ ] `request()`
- [ ] `fetch()`
- [ ] `ajax()`

@ -0,0 +1,16 @@
*Complete this quiz in class.*
1. You can fetch data from a server synchronously in a browser
- [ ] true
- [ ] false
2. What's the most common format used to exchange *data* on the web?
- [ ] HTML
- [ ] XML
- [ ] JSON
3. There's no way to prevent a web page from accessing a public server API
- [ ] true
- [ ] false

@ -0,0 +1,322 @@
# Methods of Fetching and Using Data
## [Pre-lecture quiz](.github/pre-lecture-quiz.md)
### Introduction
At the core of every web application there's *data*. Data can take many forms, but the end goal here is always to display information to the user. With web apps becoming increasingly interactive and complex, how the user access and interact with information is now a key part of web development.
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.
### Prerequisite
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.
You can test that the server is running properly by executing this command in a terminal:
```sh
curl http://localhost:5000/api
# -> should return "Bank API v1.0.0" as a result
```
---
## AJAX and data fetching
Traditional web sites update the content displayed when the user click on a link or submit 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*.
![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/en-US/docs/Web/API/Document_Object_Model) API. Over time, this approach has evolved into what we is now called a [*Single-Page Application* or *SPA*](https://en.wikipedia.org/wiki/Single-page_application).
![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/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest). But modern browsers now also implement the more convenient and powerful [`Fetch` API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API), which use promises and is better suited to manipulate JSON data.
> While all modern browsers supports 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.
### Task:
In [the previous lesson](../2-forms/README.md) we implented 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:
```js
async function login() {
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.
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:
```js
async function getAccount(user) {
try {
const response = await fetch('//localhost:5000/api/accounts/' + encodeURIComponent(user));
return await response.json();
} catch (error) {
return { error: error.message || 'Unknown error' };
}
}
```
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` create a [`GET`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET) HTTP request, which is what we are seeking here.
`encodeURIComponent()` is a function that escapes special characters for URL. What possible issues could we possibly have if we do not call this function and use directly the `user` value in the URL?
Let's now update our `login` function to use `getAccount`:
```js
async function login() {
const loginForm = document.getElementById('loginForm')
const user = loginForm.user.value;
const data = await getAccount(user);
if (data.error) {
return console.log('loginError', data.error);
}
account = data;
navigate('/dashboard');
}
```
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 errors cases. For now we'll only add a log message to display the error, and come back to it layer.
Then we have to store the data somewhere so we can later use it to display the dashboard informations. Since we the `account` variable does not exist yet, we'll create a global variable for it at the top of our file:
```js
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.
Finally, we need to call our `login` function when the login form is submitted by modifying the HTML:
```html
<form id="loginForm" action="javascript:login()">
```
Test that everything is working correctly by registering a new account and trying to login using the same account.
Before moving on to the next part, we can also complete the `register` function by adding this at the bottom of the function:
```js
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/en-US/docs/Web/HTTP/CORS) it is possible to perform cross-origin HTTP requests is the server add specials headers to the response, allowing exceptions for specific domains.
## Update HTML to display data
Now that we have the user data, we have to update the existing HTML to display it. We already now how to retrieve 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:
- Using the [`textContent`](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent) property you can change the text of an element. Note that changing this values removes all the element's children (if there's any) and replace 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.
- Using [`document.createElement()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement) along with the [`append()`](https://developer.mozilla.org/en-US/docs/Web/API/ParentNode/append) method you can create and attach one or more new child elements.
✅ Using the [`innerHTML`](https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML) property of an element it's also possible to change its HTML contents, but this one should avoided as it's vulnerable to [cross-site scripting (XSS)](https://developer.mozilla.org/en-US/docs/Glossary/Cross-site_scripting) attacks.
### Task
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.
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>`:
```html
...
<div id="loginError"></div>
<button>Login</button>
...
```
This `<div>` element is empty, meaning that nothing we 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.
Go back to the `app.js` file and create a new helper function `updateElement`:
```js
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:
```js
if (data.error) {
return updateElement('loginError', data.error);
}
```
Now if you try to login with an invalid account, you should see something like this:
![Screenshot showing the error message displayed during login](./images/login-error.png)
Implement the same behavior for the `register` function errors (don't forget to update the HTML).
## Display information on the dashboard
Using the same techniques we've just seen we'll also take care of displaying the account information on the the dashboard page.
This is what an account object received from the server looks like:
```json
{
"user": "test",
"currency": "$",
"description": "Test account",
"balance": 75,
"transactions": [
{ "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.
### Task
Let's start by replacing the "Balance" section in the HTML to add placeholder elements:
```html
<section>
Balance: <span id="balance"></span><span id="currency"></span>
</section>
```
We'll also add a new section just below to display the account description:
```html
<section id="description" aria-label="Account description"></section>
```
✅ As there is no text label here to explain what this section is about, we use the `aria-label` attribute to give an accessibility hint. Learn more about [ARIA attributes](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA) to make sure your web apps are accessible to everyone.
Next, we'll create a new function in `app.js` to fill in the placeholder:
```js
function updateDashboard() {
if (!account) {
return navigate('/login');
}
updateElement('description', account.description);
updateElement('balance', account.balance.toFixed(2));
updateElement('currency', account.currency);
}
```
First, we check that we have the account data we need before going futher. Then we use the `updateElement()` function we created earlier to update the HTML.
> To make the balance display prettier, we use the method [`toFixed(2)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed) to force displaying the value with 2 digits after the decimal point.
Now we need to call our `updateDashboard()` function everytime the dashboard is loaded. If you already finished the [lesson 1 assignment](../1-template-route/assignment.md) this should be straighforward, otherwise you can use the following implementation.
Add this code to the end of the `updateRoute()` function:
```js
if (typeof route.init === 'function') {
route.init();
}
```
And update the routes definition with:
```js
const routes = {
'/login': { templateId: 'login' },
'/dashboard': { templateId: 'dashboard', init: updateDashboard }
};
```
With this change, everytime the dashboard page is displayed then the function `updateDashboard()` is called. After a login, you should then be able to see the account balance, currency and description.
## Create table rows dynamically with HTML templates
In the [first lesson](../1-template-route/README.md) we used HTML template along with the [`appendChild()`](https://developer.mozilla.org/en-US/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.
We'll use a similar approach to display the list of transactions in the HTML table.
### Task
Add a new template in the HTML `<body>`:
```html
<template id="transaction">
<tr>
<td></td>
<td></td>
<td></td>
</tr>
</template>
```
This template represents a single table row, with the 3 columns we want to populate: *date*, *object* and *amount* of a transaction.
Then, add this `id` property to the `<tbody>` element of the table within the dashboard template to make it easier to find using JavaScript:
```html
<tbody id="transactions"></tbody>
```
Our HTML is ready, let's switch to JavaScript code and create a new function `createTransactionRow`:
```js
function createTransactionRow(transaction) {
const template = document.getElementById('transaction');
const transactionRow = template.content.cloneNode(true);
const tr = transactionRow.querySelector('tr');
tr.children[0].textContent = transaction.date;
tr.children[1].textContent = transaction.object;
tr.children[2].textContent = transaction.amount.toFixed(2);
return transactionRow;
}
```
This function does exactly what its names implies: using the template we created earlier, it create a new table row and fill in its contents using transaction data. We'll use this to in our `updateDashboard()` function to populate the table:
```js
const transactionsRows = document.createDocumentFragment();
for (const transaction of account.transactions) {
const transactionRow = createTransactionRow(transaction);
transactionsRows.appendChild(transactionRow);
}
updateElement('transactions', transactionsRows);
```
We use here the method [`document.createDocumentFragment()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createDocumentFragment) that create a new DOM fragment on which we can work, before finally attaching it to our HTML table.
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:
```js
function updateElement(id, textOrNode) {
const element = document.getElementById(id);
element.textContent = ''; // Removes all children
element.append(textOrNode);
}
```
We use the [`append()`](https://developer.mozilla.org/en-US/docs/Web/API/ParentNode/append) method as it allows to attach either text or [DOM Nodes](https://developer.mozilla.org/en-US/docs/Web/API/Node) to a parent element, which is perfect for all our use cases.
If you try using the `test` account to login, you should now see a transaction list on the dashboard 🎉.
---
## 🚀 Challenge
Work together to make the dashboard page look like a real banking app. If you already styled your app, try to use [media queries](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries) to create a [responsive design](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Responsive/responsive_design_building_blocks) working nicely on both desktop and mobile devices.
Here's an example of a styled dashboard page:
![Screenshot of an example result of the dashboard after styling](../images/screen2.png)
## [Post-lecture quiz](.github/post-lecture-quiz.md)
## Assignment
[Refactor and comment your code](assignment.md)

@ -0,0 +1,15 @@
# Refactor and comment your code
## 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:
- 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
## Rubric
| 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. |

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

@ -0,0 +1,59 @@
# [Tema de la lección]
! [Insertar un video aquí](video-url)
## [Prueba previa a la conferencia](.github/prelecture-quiz.md)
[Describe lo que aprenderemos]
### Introducción
Describe lo que se cubrirá
- XHR y buscar
- Análisis JSON
- visualización de datos
- plantilla de fila de tabla
- asignación: comentario + código de refactorización
- desafío: hazlo bonito
> Notas
### Requisito previo
¿Qué pasos deberían haberse cubierto antes de esta lección?
### Preparación
Pasos preparatorios para comenzar esta lección
---
[Recorrer el contenido en bloques]
## [Tema 1]
### Tarea:
Trabajen juntos para mejorar progresivamente su base de código para construir el proyecto con código compartido:
`` `html
bloques de código
''
✅ Comprobación de conocimientos: aproveche este momento para ampliar el conocimiento de los estudiantes con preguntas abiertas
## [Tema 2]
## [Tema 3]
🚀 Desafío: agregue un desafío para que los estudiantes trabajen en colaboración en clase para mejorar el proyecto
Opcional: agregue una captura de pantalla de la interfaz de usuario de la lección completa si corresponde
## [Prueba posterior a la conferencia](.github/post-lecture-quiz.md)
## Revisión y autoestudio
**Vencimiento de la asignación [MM/AA]**: [Nombre de la asignación](assignment.md)

@ -0,0 +1,15 @@
# Refactoriza y comenta tu código
## Instrucciones
A medida que su código base crece, es importante refactorizar su código con frecuencia para mantenerlo legible y mantenible con el tiempo. Agregue comentarios y refactorice su ʻapp.js` para mejorar la calidad del código:
- Extraer constantes, como la URL base de la API del servidor
- Factorizar código similar: por ejemplo, puede crear una función `sendRequest()` para reagrupar el código utilizado tanto en `createAccount()` como en `getAccount()`
- Reorganice el código para que sea más fácil de leer y agregue comentarios
## Rúbrica
| Criterios | Ejemplar | Adecuado | Necesita mejorar |
| -------- | -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- ------- | -------------------------------------------------- ----------------------------------------------- | -------------------------------------------------- ----------------------------------- |
| | El código está comentado, bien organizado en diferentes secciones y fácil de leer. Se extraen las constantes y se ha creado una función factorizada `sendRequest()`. | El código está limpio pero aún se puede mejorar con más comentarios, extracción constante o factorización. | El código es desordenado, no comentado, las constantes no se extraen y el código no se factoriza. |

@ -0,0 +1,17 @@
*Complete this quiz by checking one answer per question.*
1. What do you think *state management* means?
- [ ] Enforcement of law & order
- [ ] Logging the user interface state over time
- [ ] Keeping clean your app data flow
2. How can you keep track of the user session state?
- [ ] HTTP cookies
- [ ] Local or session storage
- [ ] All of the above
3. Mutating an object is always the best way to update it
- [ ] true
- [ ] false

@ -0,0 +1,18 @@
*Complete this quiz in class.*
1. What is an immutable object?
- [ ] An object defined as a constant
- [ ] An object that cannot be modified after it's created
- [ ] A copy of an existing object
2. What benefit(s) you get from using state management?
- [ ] You can keep track of every place the state is updated
- [ ] It's easier to debug the code
- [ ] All of the above
3. What's the best way to persist user data across different sessions?
- [ ] Using files
- [ ] Using the browser's `localStorage` API
- [ ] In a database behind a server API

@ -0,0 +1,60 @@
# [Lesson Topic]
![Embed a video here](video-url)
## [Pre-lecture quiz](.github/pre-lecture-quiz.md)
[Describe what we will learn]
### Introduction
Describe what will be covered
- logged in or not
- account data
- let state = {}
- immutability
- local storage
- assignment: add transaction
> Notes
### Prerequisite
What steps should have been covered before this lesson?
### Preparation
Preparatory steps to start this lesson
---
[Step through content in blocks]
## [Topic 1]
### Task:
Work together to progressively enhance your codebase to build the project with shared code:
```html
code blocks
```
✅ Knowledge Check - use this moment to stretch students' knowledge with open questions
## [Topic 2]
## [Topic 3]
🚀 Challenge: Add a challenge for students to work on collaboratively in class to enhance the project
Optional: add a screenshot of the completed lesson's UI if appropriate
## [Post-lecture quiz](.github/post-lecture-quiz.md)
## Review & Self Study
**Assignment Due [MM/YY]**: [Assignment Name](assignment.md)

@ -0,0 +1,10 @@
# [Assignment Name]
## Instructions
## Rubric
| Criteria | Exemplary | Adequate | Needs Improvement |
| -------- | --------- | -------- | ----------------- |
| | | |
| |

@ -0,0 +1,60 @@
# [Tema de la lección]
![Insertar un video aquí](video-url)
## [Prueba previa a la conferencia](.github/prelecture-quiz.md)
[Describe lo que aprenderemos]
### Introducción
Describe lo que se cubrirá
- iniciado sesión o no
- datos de la cuenta
- dejar estado = {}
- inmutabilidad
- almacenamiento local
- asignación: agregar transacción
> Notas
### Requisito previo
¿Qué pasos deberían haberse cubierto antes de esta lección?
### Preparación
Pasos preparatorios para comenzar esta lección
---
[Recorrer el contenido en bloques]
## [Tema 1]
### Tarea:
Trabajen juntos para mejorar progresivamente su base de código para construir el proyecto con código compartido:
```html
bloques de código
```
✅ Comprobación de conocimientos: aproveche este momento para ampliar el conocimiento de los estudiantes con preguntas abiertas
## [Tema 2]
## [Tema 3]
🚀 Desafío: agregue un desafío para que los estudiantes trabajen en colaboración en clase para mejorar el proyecto
Opcional: agregue una captura de pantalla de la interfaz de usuario de la lección completa si corresponde
## [Prueba posterior a la conferencia](.github/post-lecture-quiz.md)
## Revisión y autoestudio
**Asignación vence [MM/AA]**: [Nombre de asignación](assignment.md)

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 WebDev-For-Beginners
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,20 @@
# :dollar: Build a Bank
In this project, you'll learn how to build a fictional bank. These lessons include instructions on how to layout a web app and provide routes, build forms, manage state, and fetch data from an API from which you can fetch the bank's data.
<img src="images/screen1.png" width="50%" height="auto"/><img src="images/screen2.png" width="50%" height="auto"/>
## Lessons
1. [HTML Templates and Routes in a Web App](1-template-route/README.md)
2. [Build a Login and Registration Form](2-forms/README.md)
3. [Methods of Fetching and Using Data](3-data/README.md)
4. [Concepts of State Management](4-state-management/README.md)
### Credits
These lessons were written with :hearts: by [Yohan Lasorsa](https://twitter.com/sinedied).
If you're interested to learn how to build the [server API](./api/README) used in these lessons, you can follow [this series of videos](https://aka.ms/NodeBeginner) (in particular videos 17 through 21).
You can also take a look at [this interactive Learn tutorial](https://aka.ms/learn/express-api).

@ -0,0 +1,13 @@
{
"env": {
"commonjs": true,
"es2020": true,
"node": true
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 11
},
"rules": {
}
}

@ -0,0 +1,4 @@
node_modules/
*.logs
.DS_Store
.Thumbs.db

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2020 Yohan Lasorsa (@sinedied)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,33 @@
# Bank API
> Bank API built with [Node.js](https://nodejs.org) + [Express](https://expressjs.com/).
The API is already built for you and not part of the exercise.
However, if you're interested to learn how to build an API like this you can follow this series of videos: https://aka.ms/NodeBeginner (videos 17 through 21 covers this exact API).
You can also take a look at this interactive tutorial: https://aka.ms/learn/express-api
## Running the server
Make sure you have [Node.js](https://nodejs.org) installed.
1. Git clone this repo.
2. Open a terminal in `api` folder, then run `npm install`.
3. Run `npm start`.
The server should start listening on port `5000`.
> Note: all entries are stored in-memory and are not persisted, so when the server is stopped all data is lost.
## API details
Route | Description
---------------------------------------------|------------------------------------
GET /api/ | Get server info
POST /api/accounts/ | Create an account, ex: `{ user: 'Yohan', description: 'My budget', currency: 'EUR', balance: 100 }`
GET /api/accounts/:user | Get all data for the specified account
DELETE /api/accounts/:user | Remove specified account
POST /api/accounts/:user/transactions | Add a transaction, ex: `{ date: '2020-07-23T18:25:43.511Z', object: 'Bought a book', amount: -20 }`
DELETE /api/accounts/:user/transactions/:id | Remove specified transaction

@ -0,0 +1,34 @@
# You need REST Client extension for VS Code to use this file
# Download at https://aka.ms/vscode/rest-client
GET http://localhost:5000/api/
###
POST http://localhost:5000/api/accounts/
Content-Type: application/x-www-form-urlencoded
user=sinedied&currency=$&balance=50
###
GET http://localhost:5000/api/accounts/sinedied
###
DELETE http://localhost:5000/api/accounts/sinedied
###
POST http://localhost:5000/api/accounts/sinedied/transactions
Content-Type: application/json
{
"date": "2020-07-24T11:09:17.781Z",
"object": "Bought book",
"amount": -20
}
###
DELETE http://localhost:5000/api/accounts/sinedied/transactions/8825ff3e8331277911174fd1b73ff889

File diff suppressed because it is too large Load Diff

@ -0,0 +1,30 @@
{
"name": "bank-api",
"version": "1.0.0",
"description": "Bank API",
"private": true,
"main": "server.js",
"scripts": {
"start": "node server.js",
"lint": "eslint",
"format": "prettier --single-quote --write *.js"
},
"keywords": [],
"author": {
"name": "Yohan Lasorsa",
"url": "https://twitter.com/sinedied"
},
"license": "MIT",
"devDependencies": {
"eslint": "^7.5.0",
"prettier": "^2.0.5"
},
"dependencies": {
"body-parser": "^1.19.0",
"cors": "^2.8.5",
"express": "^4.17.1"
},
"engines": {
"node": ">=10"
}
}

@ -0,0 +1,197 @@
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors')
const crypto = require('crypto');
const pkg = require('./package.json');
// App constants
const port = process.env.PORT || 5000;
const apiPrefix = '/api';
// Store data in-memory, not suited for production use!
const db = {
test: {
user: 'test',
currency: '$',
description: `Test account`,
balance: 75,
transactions: [
{ 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 }
],
}
};
// Create the Express app & setup middlewares
const app = express();
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(cors({ origin: /http:\/\/localhost/ }));
app.options('*', cors());
// ***************************************************************************
// Configure routes
const router = express.Router();
// Get server infos
router.get('/', (req, res) => {
return res.send(`${pkg.description} v${pkg.version}`);
});
// ----------------------------------------------
// Create an account
router.post('/accounts', (req, res) => {
// Check mandatory request parameters
if (!req.body.user || !req.body.currency) {
return res.status(400).json({ error: 'Missing parameters' });
}
// Check if account already exists
if (db[req.body.user]) {
return res.status(409).json({ error: 'User already exists' });
}
// Convert balance to number if needed
let balance = req.body.balance;
if (balance && typeof balance !== 'number') {
balance = parseFloat(balance);
if (isNaN(balance)) {
return res.status(400).json({ error: 'Balance must be a number' });
}
}
// Create account
const account = {
user: req.body.user,
currency: req.body.currency,
description: req.body.description || `${req.body.user}'s budget`,
balance: balance || 0,
transactions: [],
};
db[req.body.user] = account;
return res.status(201).json(account);
});
// ----------------------------------------------
// Get all data for the specified account
router.get('/accounts/:user', (req, res) => {
const account = db[req.params.user];
// Check if account exists
if (!account) {
return res.status(404).json({ error: 'User does not exist' });
}
return res.json(account);
});
// ----------------------------------------------
// Remove specified account
router.delete('/accounts/:user', (req, res) => {
const account = db[req.params.user];
// Check if account exists
if (!account) {
return res.status(404).json({ error: 'User does not exist' });
}
// Removed account
delete db[req.params.user];
res.sendStatus(204);
});
// ----------------------------------------------
// Add a transaction to a specific account
router.post('/accounts/:user/transactions', (req, res) => {
const account = db[req.params.user];
// Check if account exists
if (!account) {
return res.status(404).json({ error: 'User does not exist' });
}
// Check mandatory requests parameters
if (!req.body.date || !req.body.object || !req.body.amount) {
return res.status(400).json({ error: 'Missing parameters' });
}
// Convert amount to number if needed
let amount = req.body.amount;
if (amount && typeof amount !== 'number') {
amount = parseFloat(amount);
}
// Check that amount is a valid number
if (amount && isNaN(amount)) {
return res.status(400).json({ error: 'Amount must be a number' });
}
// Generates an ID for the transaction
const id = crypto
.createHash('md5')
.update(req.body.date + req.body.object + req.body.amount)
.digest('hex');
// Check that transaction does not already exist
if (account.transactions.some((transaction) => transaction.id === id)) {
return res.status(409).json({ error: 'Transaction already exists' });
}
// Add transaction
const transaction = {
id,
date: req.body.date,
object: req.body.object,
amount,
};
account.transactions.push(transaction);
// Update balance
account.balance += transaction.amount;
return res.status(201).json(transaction);
});
// ----------------------------------------------
// Remove specified transaction from account
router.delete('/accounts/:user/transactions/:id', (req, res) => {
const account = db[req.params.user];
// Check if account exists
if (!account) {
return res.status(404).json({ error: 'User does not exist' });
}
const transactionIndex = account.transactions.findIndex(
(transaction) => transaction.id === req.params.id
);
// Check if transaction exists
if (transactionIndex === -1) {
return res.status(404).json({ error: 'Transaction does not exist' });
}
// Remove transaction
account.transactions.splice(transactionIndex, 1);
res.sendStatus(204);
});
// ***************************************************************************
// Add 'api` prefix to all routes
app.use(apiPrefix, router);
// Start the server
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});

@ -0,0 +1,32 @@
# API bancaria
> API bancaria construida con [Node.js](https://nodejs.org) + [Express](https://expressjs.com/).
La API ya está creada para usted y no forma parte del ejercicio.
Sin embargo, si está interesado en aprender cómo construir una API como esta, puede seguir esta serie de videos: https://aka.ms/NodeBeginner (los videos 17 a 21 cubren esta API exacta).
También puede echar un vistazo a este tutorial interactivo: https://aka.ms/learn/express-api
## Ejecutando el servidor
Asegúrese de tener [Node.js](https://nodejs.org) instalado.
1. Git clone este repositorio.
2. Abra una terminal en la carpeta `api`, luego ejecute `npm install`.
3. Ejecute `npm start`.
El servidor debería comenzar a escuchar en el puerto `5000`.
> Nota: todas las entradas se almacenan en la memoria y no se conservan, por lo que cuando se detiene el servidor, se pierden todos los datos.
## API
Ruta | Descripción
---------------------------------------------|------------------------------------
GET /api/ | Obtener información del servidor
POST /api/accounts/ | Cree una cuenta, por ejemplo: `{ user: 'Yohan', description: 'My budget', currency: 'EUR', balance: 100 }`
GET /api/accounts/:user | Obtener todos los datos de la cuenta especificada
DELETE /api/accounts/:user | Eliminar cuenta especificada
POST /api/accounts/:user/transactions | Agregar una transacción, por ejemplo: `{ date: '2020-07-23T18:25:43.511Z', object: 'Bought a book', amount: -20 }`
DELETE /api/accounts/:user/transactions/:id | Eliminar transacción especificada

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

@ -0,0 +1,13 @@
# Bank app
> Example solution for the bank app project, built with vanilla HTML5, CSS and JavaScript (no frameworks or libraries used).
## Running the app
First make sure you have the [API server](../api/README.md) running.
Any web server can be used to run the app, but since you should have [Node.js](https://nodejs.org) installed anyway to run the API, you can:
1. Git clone this repo.
2. Open a terminal, then run `npx lite-server solution`. It will start a development web server on port `3000`
3. Open `http://localhost:3000` in a browser to run the app.

@ -0,0 +1,271 @@
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
const serverUrl = 'http://localhost:5000/api';
const storageKey = 'savedState';
// ---------------------------------------------------------------------------
// Router
// ---------------------------------------------------------------------------
const routes = {
'/dashboard': { title: 'My Account', templateId: 'dashboard', init: refresh },
'/login': { title: 'Login', templateId: 'login' }
};
function navigate(path) {
window.history.pushState({}, path, window.location.origin + path);
updateRoute();
}
function updateRoute() {
const path = window.location.pathname;
const route = routes[path];
if (!route) {
return navigate('/dashboard');
}
const template = document.getElementById(route.templateId);
const view = template.content.cloneNode(true);
const app = document.getElementById('app');
app.innerHTML = '';
app.appendChild(view);
if (typeof route.init === 'function') {
route.init();
}
document.title = route.title;
}
// ---------------------------------------------------------------------------
// API interactions
// ---------------------------------------------------------------------------
async function sendRequest(api, method, body) {
try {
const response = await fetch(serverUrl + api, {
method: method || 'GET',
headers: body ? { 'Content-Type': 'application/json' } : undefined,
body
});
return await response.json();
} catch (error) {
return { error: error.message || 'Unknown error' };
}
}
async function getAccount(user) {
return sendRequest('/accounts/' + encodeURIComponent(user));
}
async function createAccount(account) {
return sendRequest('/accounts', 'POST', account);
}
async function createTransaction(user, transaction) {
return sendRequest('/accounts/' + user + '/transactions', 'POST', transaction);
}
// ---------------------------------------------------------------------------
// Global state
// ---------------------------------------------------------------------------
let state = {
user: null,
account: null
};
function updateState(newState) {
state = newState;
localStorage.setItem(storageKey, JSON.stringify(state));
}
// ---------------------------------------------------------------------------
// Login/register
// ---------------------------------------------------------------------------
async function login() {
const loginForm = document.getElementById('loginForm')
const user = loginForm.user.value;
const data = await getAccount(user);
if (data.error) {
return updateElement('loginError', data.error);
}
const newState = {
...state,
user: data.user
};
updateState(newState);
navigate('/dashboard');
}
async function register() {
const registerForm = document.getElementById('registerForm');
const formData = new FormData(registerForm);
const data = Object.fromEntries(formData);
const jsonData = JSON.stringify(data);
const result = await createAccount(jsonData);
if (result.error) {
return updateElement('registerError', result.error);
}
const newState = {
...state,
user: result.user
};
updateState(newState);
navigate('/dashboard');
}
// ---------------------------------------------------------------------------
// Dashboard
// ---------------------------------------------------------------------------
async function updateAccountData() {
const user = state.user;
if (!user) {
return logout();
}
const data = await getAccount(user);
if (data.error) {
if (data.error === 'User does not exist') {
return logout();
}
return updateElement('dashboardError', data.error);
}
const newState = {
...state,
account: data
};
updateState(newState);
}
async function refresh() {
await updateAccountData();
updateDashboard();
}
function updateDashboard() {
const account = state.account;
if (!account) {
return logout();
}
updateElement('description', account.description);
updateElement('balance', account.balance.toFixed(2));
updateElement('currency', account.currency);
// Update transactions
const transactionsRows = document.createDocumentFragment();
for (const transaction of account.transactions) {
const transactionRow = createTransactionRow(transaction);
transactionsRows.appendChild(transactionRow);
}
updateElement('transactions', transactionsRows);
}
function createTransactionRow(transaction) {
const template = document.getElementById('transaction');
const transactionRow = template.content.cloneNode(true);
const tr = transactionRow.querySelector('tr');
tr.children[0].textContent = transaction.date;
tr.children[1].textContent = transaction.object;
tr.children[2].textContent = transaction.amount.toFixed(2);
return transactionRow;
}
function addTransaction() {
const dialog = document.getElementById('transactionDialog');
dialog.classList.add('show');
// Reset form
const transactionForm = document.getElementById('transactionForm');
transactionForm.reset();
// Set date to today
transactionForm.date.valueAsDate = new Date();
}
async function confirmTransaction() {
const dialog = document.getElementById('transactionDialog');
dialog.classList.remove('show');
const transactionForm = document.getElementById('transactionForm');
const formData = new FormData(transactionForm);
const jsonData = JSON.stringify(Object.fromEntries(formData));
const data = await createTransaction(state.user, jsonData);
if (data.error) {
return updateElement('transactionError', data.error);
}
// Update local state with new transaction
const newState = {
...state,
account: {
...state.account,
balance: state.account.balance + data.amount,
transactions: [...state.account.transactions, data]
}
}
updateState(newState);
// Update display
updateDashboard();
}
async function cancelTransaction() {
const dialog = document.getElementById('transactionDialog');
dialog.classList.remove('show');
}
function logout() {
const newState = {
user: null,
account: null
};
updateState(newState);
navigate('/login');
}
// ---------------------------------------------------------------------------
// Utils
// ---------------------------------------------------------------------------
function updateElement(id, textOrNode) {
const element = document.getElementById(id);
element.textContent = ''; // Removes all children
element.append(textOrNode);
}
// ---------------------------------------------------------------------------
// Init
// ---------------------------------------------------------------------------
function init() {
// Restore state
const savedState = localStorage.getItem(storageKey);
if (savedState) {
updateState(JSON.parse(savedState));
}
// Update route for browser back/next buttons
window.onpopstate = () => updateRoute();
updateRoute();
}
init();

@ -0,0 +1,110 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Squirrel Banking</title>
<link rel="stylesheet" href="styles.css">
<script src="app.js" defer></script>
</head>
<body>
<!-- Placeholder where we will insert our app HTML based on route -->
<div id="app">Loading...</div>
<!-- Login page template -->
<template id="login">
<section class="login-page">
<div class="login-container">
<div class="login-title text-center">
<span class="hide-xs">Squirrel</span>
<img class="login-logo" src="logo.svg" alt="Squirrel Banking Logo">
<span class="hide-xs">Banking</span>
</div>
<div class="login-content">
<h2 class="text-center">Login</h2>
<form id="loginForm" action="javascript:login()">
<label for="user">Username</label>
<input name="user" type="text" maxlength="20" required>
<div id="loginError" class="error"></div>
<button>Login</button>
</form>
<p class="login-separator text-center"><span>OR</span></p>
<h2 class="text-center">Register</h2>
<form id="registerForm" action="javascript:register()">
<label for="user">Username</label>
<input name="user" type="text" maxlength="20" required>
<label for="currency">Currency</label>
<input name="currency" type="text" maxlength="5" value="$" required>
<label for="description">Description</label>
<input name="description" type="text" maxlength="100">
<label for="balance">Current balance</label>
<input name="balance" type="number" value="0">
<div id="registerError" class="error"></div>
<button>Register</button>
</form>
</div>
</div>
</section>
</template>
<!-- Dashboard page template -->
<template id="dashboard">
<section class="dashboard-page">
<header class="dashboard-header">
<img class="dashboard-logo" src="logo.svg" alt="Squirrel Banking Logo">
<span class="dashboard-title hide-xs">Squirrel Banking</span>
<button onclick="logout()">Logout</button>
</header>
<div class="balance">
<div>Balance</div>
<span id="balance"></span>
<span id="currency"></span>
</div>
<div class="dashboard-content">
<div class="transactions-title">
<div id="description" aria-label="Account description"></div>
<button onclick="addTransaction()">Add transaction</button>
</div>
<table class="transactions-table" aria-label="Transactions">
<thead>
<tr>
<th>Date</th>
<th>Object</th>
<th>Amount</th>
</tr>
</thead>
<tbody id="transactions"></tbody>
</table>
<p id="dashboardError" class="error"></p>
</div>
</section>
<section id="transactionDialog" class="dialog">
<div class="dialog-content">
<h2 class="text-center">Add transaction</h2>
<form id="transactionForm" action="javascript:void(0)">
<label for="date">Date</label>
<input name="date" type="date" required>
<label for="object">Object</label>
<input name="object" type="text" maxlength="50" required>
<label for="amount">Amount (use negative value for debit)</label>
<input name="amount" type="number" value="0" step="any" required>
<div id="transactionError" class="error"></div>
<div class="dialog-buttons">
<button class="button-alt" formaction="javascript:cancelTransaction()" formnovalidate>Cancel</button>
<button formaction="javascript:confirmTransaction()">OK</button>
</div>
</form>
</div>
</section>
</template>
<!-- Transaction row template -->
<template id="transaction">
<tr>
<td></td>
<td></td>
<td></td>
</tr>
</template>
</body>
</html>

@ -0,0 +1 @@
<svg id="squirrel" data-name="squirrel" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 296.2 479.27"><defs><style>.cls-1{fill: #fff;}</style></defs><path class="cls-1" d="M216.54,17c-30.06,93.06,63.25,82.67,127,133.73,48.38,38.78,92.87,118,29,234.83-11.12-29-24.49-75.4-37.9-138.2-6.71-31.52-11.1-92.35-12.16-91.7-6.88,4.25-21,37-21,37s-2.43-.6-13.36-2.83S256,187,256,187s-21.09.6-32,2.83-13.36,2.83-13.36,2.83-14.16-32.78-21-37c-1.06-.65-5.45,60.18-12.17,91.7-6.93,32.5-15.7,65.2-23.28,91.19C79.26,248.12,92.5,102.73,216.54,17Zm85.82,254.1c3.84,0,7-6.14,7-13.71s-3.12-13.71-7-13.71-6.95,6.14-6.95,13.71,3.12,13.71,6.95,13.71Zm-85.71-13.71c0-7.57-3.12-13.71-7-13.71s-6.95,6.14-6.95,13.71,3.11,13.71,6.95,13.71,7-6.14,7-13.71Zm59,23.45c0-6.37-8.78-11.53-19.62-11.53s-19.62,5.16-19.62,11.53c0,5.88,7.52,10.74,17.24,11.43v8a2.39,2.39,0,0,0,2.38,2.39h0a2.39,2.39,0,0,0,2.38-2.39v-8c9.71-.69,17.24-5.55,17.24-11.43Zm-7.72,34.51c-2.6-.18-6.08,4.76-6.93,8.07s-4,26.07-4,26.07-22.8-5.74-38.06-6-23,5.77-28.09,13.74-2.22,18.75-.44,23a21.08,21.08,0,0,0,3.63,6.14,88.82,88.82,0,0,0-5,22.13c7.8,10.18,33.56,26,33.56,26s6.54,3.47,5.38,9.55-18,13.91-30.55,11.74c-.53-.09-1.09-.21-1.68-.35C206.45,478.28,227,496.27,227,496.27s40.86-2.08,64-21.89a100.57,100.57,0,0,0,8.1-7.77c-7-2.86-13.59-5.9-17.28-8.48-10.47-7.29-16.92-24.69-13.48-29.82s10.54-3,10.54-3,29.43,6.88,42.13,5.11a21.1,21.1,0,0,0,6.65-2.57c4-2.22,13-8.87,13.88-18.31S340.1,390.61,328,381.35s-33.59-18.88-33.59-18.88,11.6-19.83,13-23,1.7-9.17-.44-10.63-19.05-8.05-19.05-8.05S270.52,315.53,267.93,315.35Z" transform="translate(-108.03 -17)"/></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

@ -0,0 +1,351 @@
:root {
/* Colors */
--primary: #0091ea;
--primary-light: #7bbde6;
--accent: #546e7a;
--grey: #445;
--error: #f52;
--background: #f5f5f6;
--background-accent: #cfe5f2;
--white: #fff;
--border: #99a;
/* Sizes */
--radius: 10px;
--space-xs: 5px;
--space-sm: 10px;
--space-md: 20px;
--space-xl: 40px;
}
/* ------------------------------------------------------------------------ */
/* Micro reset */
/* ------------------------------------------------------------------------ */
* {
box-sizing: border-box;
}
html, body, #app {
margin: 0;
padding: 0;
height: 100%;
}
/* ------------------------------------------------------------------------ */
/* General styles */
/* ------------------------------------------------------------------------ */
body {
font-family: Arial, Helvetica, sans-serif;
font-size: 14px;
}
h2 {
color: var(--primary);
text-transform: uppercase;
font-weight: bold;
font-size: 1.5rem;
margin: var(--space-md) 0;
}
form {
display: flex;
flex-direction: column;
margin: var(--space-sm) var(--space-md);
}
input {
margin-top: var(--space-xs);
margin-bottom: var(--space-sm);
height: 40px;
padding: var(--space-xs) var(--space-sm);
border: 1px solid var(--border);
border-radius: var(--radius);
}
input:focus {
border-color: var(--primary);
outline: 0;
}
label {
color: var(--grey);
text-transform: uppercase;
font-size: 80%;
}
button {
font-weight: bold;
background-color: var(--primary);
color: var(--white);
height: 40px;
padding: var(--space-xs);
border: 0;
border-radius: var(--radius);
text-transform: uppercase;
min-width: 100px;
margin: var(--space-sm) 0;
}
.button-alt {
background-color: transparent;
color: var(--primary);
}
button:hover {
filter: brightness(115%);
cursor: pointer;
}
button:focus {
outline: none;
border: 2px solid var(--grey);
}
.error {
color: var(--error);
margin: var(--space-xs) 0;
}
.error:empty {
display: none;
}
/* ------------------------------------------------------------------------ */
/* Login page */
/* ------------------------------------------------------------------------ */
.login-title {
font-size: 2rem;
font-weight: bold;
color: var(--white);
margin: var(--space-md);
}
.login-logo {
height: 80px;
vertical-align: middle;
}
.login-page {
display: flex;
height: 100%;
align-items: center;
justify-content: center;
background: linear-gradient(var(--primary), var(--primary-light));
}
.login-container {
flex: auto;
max-width: 480px;
max-height: 100%;
overflow: auto;
}
.login-content {
background-color: var(--background);
padding: var(--space-sm);
}
.login-separator {
position: relative;
top: 0.5em;
border-top: 1px solid var(--border);
opacity: 0.6;
margin: var(--space-md) 0;
}
.login-separator > span {
position: relative;
top: -0.5em;
background-color: var(--background);
padding: var(--space-sm);
}
/* ------------------------------------------------------------------------ */
/* Dashboard page */
/* ------------------------------------------------------------------------ */
.dashboard-page {
display: flex;
height: 100%;
flex-direction: column;
}
.dashboard-header {
background-color: var(--grey);
padding: 0 var(--space-sm)
}
.dashboard-header button {
float: right;
border: 1px solid;
background-color: transparent;
}
.dashboard-title {
font-size: 1.5rem;
font-weight: bold;
color: var(--white);
vertical-align: middle;
margin: 0 var(--space-sm)
}
.dashboard-logo {
height: 60px;
vertical-align: middle;
padding: var(--space-xs);
}
.balance {
background: radial-gradient(circle at center, var(--primary), var(--primary-light));
text-align: center;
}
.balance > div {
color: var(--white);
padding-top: var(--space-xs);
text-transform: uppercase;
}
.balance > span {
color: var(--white);
font-size: 3rem;
}
.transactions-title {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 var(--space-sm);
color: var(--accent);
font-weight: bold;
font-size: 1.5rem;
}
.transactions-title > div {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.transactions-table {
width: 100%;
font-size: 1.2rem;
padding: var(--space-sm);
margin: 0;
border-spacing: 0;
background-color: var(--background);
}
.transactions-table thead th {
border-bottom: 1px solid var(--border);
}
.transactions-table tr:nth-child(even) {
background-color: var(--background-accent);
}
.transactions-table td,
.transactions-table th {
padding: var(--space-xs) var(--space-sm);
text-align: left;
}
.transactions-table td:first-child,
.transactions-table th:first-child {
/* Make first column use the minimum width */
width: 1%;
white-space: nowrap;
}
.transactions-table td:last-child,
.transactions-table th:last-child {
text-align: right;
}
.dialog {
display: none;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
left: 0;
top: 0;
overflow: auto;
background-color: rgba(0,0,0,0.4);
animation: slideFromTop 0.3s ease-in-out;
justify-content: center;
align-items: flex-start;
}
.dialog.show {
display: flex;
}
@keyframes slideFromTop {
from {
top: -300px;
opacity: 0;
}
to {
top: 0;
opacity: 1;
}
}
.dialog-content {
flex: auto;
background-color: var(--white);
max-width: 480px;
max-height: 100%;
padding: var(--space-sm);
}
.dialog-buttons {
text-align: right;
}
/* ------------------------------------------------------------------------ */
/* Utilities */
/* ------------------------------------------------------------------------ */
.text-center {
text-align: center;
}
.hide-xs {
display: none;
}
/* ------------------------------------------------------------------------ */
/* Responsive adaptations */
/* ------------------------------------------------------------------------ */
@media only screen and (min-width: 480px) {
.hide-xs {
display: initial;
}
.login-content,
.dialog-content {
border-radius: var(--radius);
}
.dialog-content {
margin-top: var(--space-xl);
}
}
@media only screen and (min-width: 768px) {
.transactions-table {
border-radius: var(--radius);
}
.dashboard-content {
width: 100%;
max-width: 768px;
align-self: center;
}
}

@ -0,0 +1,13 @@
# Aplicación bancaria
> Solución de ejemplo para el proyecto de la aplicación bancaria, construida con HTML5 vanilla, CSS y JavaScript (sin marcos ni bibliotecas).
## Ejecutando la aplicación
Primero asegúrese de tener el [servidor API](../api/README.md) en ejecución.
Se puede usar cualquier servidor web para ejecutar la aplicación, pero dado que debe tener [Node.js]https://nodejs.org) instalado de todos modos para ejecutar la API, puede:
1. Git clone este repositorio.
2. Abra una terminal, luego ejecute `npx lite-server solution`. Iniciará un servidor web de desarrollo en el puerto `3000`
3. Abra `http://localhost: 3000` en un navegador para ejecutar la aplicación.

@ -0,0 +1,16 @@
# :dollar: Construir un banco
En este proyecto, aprenderá a construir un banco ficticio. Estas lecciones incluyen instrucciones sobre cómo diseñar un sitio web y proporcionar rutas, crear formularios, administrar el estado y obtener datos de una API desde la cual puede obtener los datos del banco.
<img src="screen1.png" width="50%" height="auto"><img src="screen2.png" width="50%" height="auto">
## Lecciones
1. [Rutas y plantillas HTML en una aplicación web](template-route/README.md)
2. [Cree un formulario de inicio de sesión y registro](forms/README.md)
3. [Conceptos de gestión de estado](state-management/README.md)
4. [Métodos de obtención y uso de datos](data/README.md)
### Credits
Estas lecciones fueron escritas con :hearts: por [Yohan Lasorsa](https://twitter.com/sinedied)

Binary file not shown.

@ -0,0 +1,18 @@
*Complete this quiz by checking one answer per question.*
1. The World Wide Web was invented by
- [ ] Tom Barnard-Loft
- [ ] Tim Berners-Lee
- [ ] Trish Berth-Pool
2. The first browser was called
- [ ] World Wide Web
- [ ] Mozilla
- [ ] Netscape
3. Browsers can store a user's browsing history
- [ ] true
- [ ] false

@ -0,0 +1,20 @@
*A warm-up quiz about browsers*
Complete this quiz in class
1. You can get browser extensions from
- [ ] WalMart
- [ ] The browser's extension store
- [ ] The App store
2. NPM stands for
- [ ] Node Package Manager
- [ ] Netscape Primary Mix
- [ ] Natural Processing Manager
3. Your browser can serve web pages both securely and insecurely
- [ ] true
- [ ] false

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 WebDev-For-Beginners
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,164 @@
# Browser Extension Project Part 1: All about Browsers
![Browser sketchnote](images/sketchnote.jpg)
> Sketchnote by [Wassim Chegham](https://dev.to/wassimchegham/ever-wondered-what-happens-when-you-type-in-a-url-in-an-address-bar-in-a-browser-3dob)
## [Pre-lecture quiz](.github/pre-lecture-quiz.md)
### Introduction:
Browser extensions add additional functionality to a browser. But before you build one, you should learn a little about how browsers do their work.
### About the browser:
In this series of lessons, you'll learn how to build a browser extension that will work on Chrome, Firefox and Edge browsers. In this part, you'll discover how browsers work and scaffold out the elements of the browser extension.
But what is a browser exactly? It is a software application that allows an end user to access content from a server and display it on web pages.
✅ A little history: the first browser was called 'WorldWideWeb' and was created by Sir Tim Berners-Lee in 1990.
![early browsers](images/earlybrowsers.jpg)
> Some early browsers, via [Karen McGrane](https://www.slideshare.net/KMcGrane/week-4-ixd-history-personal-computing)
When a user connected to the internet using a URL (Uniform Resource Locator) address, usually using Hypertext Transfer Protocol via an `http` or `https` address, the browser communicates with a web server and fetches a web page.
At this point, the browser's rendering engine displays it on the user's device, which might be a mobile phone, desktop, or laptop.
Browsers also have the ability to cache content so that it doesn't have to be retrieved from the server every time. They can record the history of a user's browsing activity, store 'cookies', which are small bits of data that contain information used to store a user's activity, and more.
A really important thing to remember about browsers is that they are not all the same! Each browser has its strengths and weaknesses, and a professional web developer needs to understand how to make web pages perform well cross-browser. This includes handling small viewports such as a mobile phone's, as well as a user who is offline.
A really useful website that you probably should bookmark in whatever browser you prefer to use is [caniuse.com](https://www.caniuse.com). When you are building web pages, it's very helpful to use caniuse's lists of supported technologies so that you can best support your users.
✅ How can you tell what browsers are most popular with your web site's user base? Check your analytics - you can install various analytics packages as part of your web development process, and they will tell you what browsers are most used by the various popular browsers.
## Browser extensions
Why would you want to build a browser extension? It's a handy thing to attach to your browser when you need quick access to tasks that you tend to repeat. For example, if you find yourself needing to check colors on the various web pages that you interact with, you might install a color-picker browser extension. If you have trouble remembering passwords, you might use a password-management browser extension.
Browser extensions are fun to develop, too. They tend to manage a finite number of tasks that they perform well.
✅ What are your favorite browser extensions? What tasks do they perform?
### Installing extensions
Before you start building, take a look at the process of building and deploying a browser extension. While each browser varies a bit in how they manage this task, the process is similar on Chrome and Firefox to this example on Edge:
![install a browser extension](images/install-on-edge.png)
In essence, the process will be:
- build your extension using `npm build`
- navigate in the browser to the extensions pane using the `...` icon on the top right
- if it's a new installation, choose `load unpacked` to upload a fresh extension from its build folder (in our case it is `/dist`)
- or, click `reload` if you are reloading the already-installed extension
✅ These instructions pertain to extensions you build yourself; to install extensions that have been released to the browser extension store associated to each browser, you should navigate to those [stores](https://microsoftedge.microsoft.com/addons/Microsoft-Edge-Extensions-Home) and install the extension of your choice.
### Get Started
You're going to build a browser extension that displays your region's carbon footprint, showing your region's energy usage and the source of the energy. The extension will have a form that collects an API key so that you can access
CO2 Signal's API.
**You need:**
- [an API key](https://www.co2signal.com/); enter your email in the box on this page and one will be sent to you
- the [code for your region](http://api.electricitymap.org/v3/zones) corresponding to the [Electricity Map](https://www.electricitymap.org/map) (in Boston, for example, I use 'US-NEISO').
- the [starter code](../start). Download the `start` folder; you will be completing code in this folder.
- [NPM](https://www.npmjs.com) - NPM is a package management tool; install it locally and the packages listed in you `package.json` file will be installed for use by your web asset
✅ Learn more about package management in this [excellent Learn module](https://docs.microsoft.com/en-us/learn/modules/create-nodejs-project-dependencies/)
Take a minute to look through the codebase:
dist
-|manifest.json (defaults set here)
-|index.html (front-end HTML markup here)
-|background.js (background JS here)
-|main.js (built JS)
src
-|index.js (your JS code goes here)
✅ Once you have your API key and Region code handy, store those somewhere in a note for future use.
### Build the HTML for the extension
This extension has two views. One to gather the API key and region code:
![extension form](images/1.png)
And the second to display the region's carbon usage:
![carbon usage](images/2.png)
Let's start by building the HTML for the form and styling it with CSS.
In the `/dist` folder, you will build a form and a result area. In the `index.html` file, populate the delineated form area:
```HTML
<form class="form-data" autocomplete="on">
<div>
<h2>New? Add your Information</h2>
</div>
<div>
<label>Region Name</label>
<input type="text" required class="region-name" />
</div>
<div>
<label>Your API Key from tmrow</label>
<input type="text" required class="api-key" />
</div>
<button class="search-btn">Submit</button>
</form>
```
This is the form where your saved information will be input and saved to local storage.
Next, create the results area; under the final form tag, add some divs:
```HTML
<div class="result">
<div class="loading">loading...</div>
<div class="errors"></div>
<div class="data"></div>
<div class="result-container">
<p><strong>Region: </strong><span class="my-region"></span></p>
<p><strong>Carbon Usage: </strong><span class="carbon-usage"></span></p>
<p><strong>Fossil Fuel Percentage: </strong><span class="fossil-fuel"></span></p>
</div>
<button class="clear-btn">Change region</button>
</div>
```
At this point, you can try a build. Make sure to install the package dependencies of this extension:
```
npm install
```
This command will use npm, the Node Package Manager, to install webpack for your extension's build process. Webpack is a bundler that handles compiling code. You can see the output of this process by looking in `/dist/main.js` - you see the code has been bundled.
For now, the extension should build and, if you deploy it into Edge as an extension, you'll see a form neatly displayed.
Congratulations, you've taken the first steps towards building a browser extension. In subsequent lessons, you'll make it more functional and useful.
---
## 🚀 Challenge
Take a look at a browser extension store and install one to your browser. You can examine its files in interesting ways. What do you discover?
## [Post-lecture quiz](.github/post-lecture-quiz.md)
## Review & Self Study
In this lesson you learned a little about the history of the web browser; take this opportunity to learn about how the inventors of the World Wide Web envisioned its use by reading more about its history. Some useful sites include:
[The History of Web Browsers](https://www.mozilla.org/en-US/firefox/browsers/browser-history/)
[History of the Web](https://webfoundation.org/about/vision/history-of-the-web/)
[An interview with Tim Berners-Lee](https://www.theguardian.com/technology/2019/mar/12/tim-berners-lee-on-30-years-of-the-web-if-we-dream-a-little-we-can-get-the-web-we-want)
## Assignment
[Restyle your extension](assignment.md)

@ -0,0 +1,11 @@
# Restyle your Extension
## Instructions
The codebase for this extension comes complete with styles, but you don't have to use them; make your extension your own by restyling it by editing its css file.
## Rubric
| Criteria | Exemplary | Adequate | Needs Improvement |
| -------- | -------------------------------------------- | --------------------- | ----------------- |
| | Code is submitted with functional new styles | Styling is incomplete | Styles are buggy |

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

@ -0,0 +1,161 @@
# Proyecto de extensión del navegador Parte 1: Todo sobre los navegadores
![Browser sketchnote](images/sketchnote.jpg)
> Sketchnote de [Wassim Chegham](https://dev.to/wassimchegham/ever-wondered-what-happens-when-you-type-in-a-url-in-an-address-bar-in-a-browser-3dob)
## [Pre-lecture prueba](.github/pre-lecture-quiz.md)
### Introducción:
Las extensiones de navegador agregan funcionalidad adicional a un navegador. Pero antes de crear uno, debe aprender un poco sobre cómo funcionan los navegadores.
### Acerca del navegador:
En esta serie de lecciones, aprenderá a crear una extensión de navegador que funcione en los navegadores Chrome, Firefox y Edge. En esta parte, descubrirá cómo funcionan los navegadores y distribuirá los elementos de la extensión del navegador.
Pero, ¿qué es exactamente un navegador? Es una aplicación de software que permite al usuario final acceder al contenido de un servidor y mostrarlo en páginas web.
✅ Un poco de historia: el primer navegador se llamó 'WorldWideWeb' y fue creado por Sir Tim Berners-Lee en 1990.
! [navegadores iniciales](images / earlybrowsers.jpg)
> Algunos de los primeros navegadores, a través de [Karen McGrane](https://www.slideshare.net/KMcGrane/week-4-ixd-history-personal-computing)
Cuando un usuario se conecta a Internet usando una dirección URL (Localizador uniforme de recursos), generalmente usando el Protocolo de transferencia de hipertexto a través de una dirección `http` o` https`, el navegador se comunica con un servidor web y busca una página web.
En este punto, el motor de renderizado del navegador lo muestra en el dispositivo del usuario, que puede ser un teléfono móvil, una computadora de escritorio o una computadora portátil.
Los navegadores también tienen la capacidad de almacenar en caché el contenido para que no sea necesario recuperarlo del servidor cada vez. Pueden registrar el historial de la actividad de navegación de un usuario, almacenar 'cookies', que son pequeños fragmentos de datos que contienen información que se utiliza para almacenar la actividad de un usuario, y más.
Una cosa realmente importante para recordar acerca de los navegadores es que no todos son iguales. Cada navegador tiene sus fortalezas y debilidades, y un desarrollador web profesional debe comprender cómo hacer que las páginas web funcionen bien en todos los navegadores. Esto incluye el manejo de pequeñas ventanas gráficas, como las de un teléfono móvil, así como de un usuario sin conexión.
Un sitio web realmente útil que probablemente debería marcar en cualquier navegador que prefiera usar es [caniuse.com](https://www.caniuse.com). Cuando está creando páginas web, es muy útil utilizar las listas de tecnologías compatibles de caniuse para que pueda ayudar mejor a sus usuarios.
✅ ¿Cómo puede saber qué navegadores son los más populares entre la base de usuarios de su sitio web? Verifique sus análisis: puede instalar varios paquetes de análisis como parte de su proceso de desarrollo web, y le dirán qué navegadores son los más utilizados por los diversos navegadores populares.
## Extensiones de navegador
¿Por qué querrías crear una extensión de navegador? Es muy útil adjuntarlo a su navegador cuando necesita acceso rápido a tareas que tiende a repetir. Por ejemplo, si necesita comprobar los colores en las distintas páginas web con las que interactúa, puede instalar una extensión de navegador de selector de color. Si tiene problemas para recordar las contraseñas, puede utilizar una extensión de navegador de administración de contraseñas.
Las extensiones de navegador también son divertidas de desarrollar. Suelen gestionar un número finito de tareas que realizan bien.
✅ ¿Cuáles son sus extensiones de navegador favoritas? ¿Qué tareas realizan?
### Instalando extensiones
Antes de empezar a crear, observe el proceso de creación e implementación de una extensión de navegador. Si bien cada navegador varía un poco en la forma en que administran esta tarea, el proceso es similar en Chrome y Firefox a este ejemplo en Edge:
![instalar una extensión de navegador](images/install-on-edge.png)
En esencia, el proceso será:
- construye tu extensión usando `npm build`
- navegue en el navegador hasta el panel de extensiones usando el icono `...` en la parte superior derecha
- si se trata de una nueva instalación, elija `load unpacked` para cargar una nueva extensión desde su carpeta de compilación (en nuestro caso es `/ dist`)
- o haga clic en `recargar` si está recargando la extensión ya instalada
✅ Estas instrucciones pertenecen a extensiones que usted mismo construye; para instalar extensiones que se han lanzado a la tienda de extensiones del navegador asociada a cada navegador, debe navegar a esas [tiendas](https://microsoftedge.microsoft.com/addons/Microsoft-Edge-Extensions-Home) e instalar la extensión de tu elección.
### Empezar
Vas a crear una extensión de navegador que muestre la huella de carbono de tu región, mostrando el uso de energía de tu región y la fuente de energía. La extensión tendrá un formulario que recopila una clave API para que pueda acceder
API de CO2 Signal.
**Necesitas:**
- [una clave API](https://www.co2signal.com/); ingrese su correo electrónico en el cuadro de esta página y se le enviará uno
- el [código de su región](http://api.electricitymap.org/v3/zones) correspondiente al [Mapa de electricidad](https://www.electricitymap.org/map) (en Boston, por ejemplo, Yo uso 'US-NEISO').
- el [código de inicio](../ inicio). Descargue la carpeta `start`; completará el código en esta carpeta.
- [NPM](https://www.npmjs.com) - NPM es una herramienta de gestión de paquetes; instálelo localmente y los paquetes enumerados en su archivo `package.json` se instalarán para que los use su recurso web
✅ Obtenga más información sobre la administración de paquetes en este [excelente módulo de aprendizaje](https://docs.microsoft.com/en-us/learn/modules/create-nodejs-project-dependencies/)
Tómate un minuto para revisar el código base:
dist
- | manifest.json (los valores predeterminados se establecen aquí)
- | index.html (marcado HTML de front-end aquí)
- | background.js (JS de fondo aquí)
- | main.js (JS construido)
src
- | index.js (su código JS va aquí)
✅ Una vez que tenga a mano su clave API y el código de región, guárdelos en algún lugar en una nota para uso futuro.
### Construye el HTML para la extensión
Esta extensión tiene dos vistas. Uno para recopilar la clave API y el código de región:
![extension form](1.png)
Y el segundo para mostrar el uso de carbono de la región:
![carbon usage](2.png)
Comencemos por construir el HTML para el formulario y darle estilo con CSS.
En la carpeta `/dist`, creará un formulario y un área de resultados. En el archivo `index.html`, complete el área delineada del formulario:
```HTML
<form class="form-data" autocomplete="on">
<div>
<h2>¿Nuevo? Agrega tu información</h2>
</div>
<div>
<label>Nombre de la región</label>
<input type="text" required class="region-name" />
</div>
<div>
<label>Tu clave API de tmrow</label>
<input type="text" required class="api-key" />
</div>
<button class="search-btn">Enviar</button>
</form>
```
Este es el formulario donde se ingresará la información guardada y se guardará en el almacenamiento local.
A continuación, cree el área de resultados; debajo de la etiqueta de formulario final, agregue algunos divs:
```HTML
<div class="result">
<div class="loading">cargando...</div>
<div class="errors"></div>
<div class="data"></div>
<div class="result-container">
<p><strong>Región: </strong><span class="my-region"></span></p>
<p><strong>Uso de carbono: </strong><span class="carbon-usage"></span></p>
<p><strong>Porcentaje de combustible fósil: </strong><span class="fossil-fuel"></span></p>
</div>
<button class="clear-btn">Cambia región</button>
</div>
```
En este punto, puede probar una compilación. Asegúrese de instalar las dependencias del paquete de esta extensión:
```
npm install
```
Este comando usará npm, el Node Package Manager, para instalar el paquete web para el proceso de compilación de su extensión. Webpack es un paquete que maneja la compilación de código. Puede ver el resultado de este proceso mirando en `/dist/main.js`; verá que el código se ha incluido.
Por ahora, la extensión debería compilarse y, si la implementa en Edge como una extensión, verá un formulario claramente mostrado.
Felicitaciones, ha dado los primeros pasos para crear una extensión de navegador. En lecciones posteriores, lo hará más funcional y útil.
🚀Challenge: Eche un vistazo a una tienda de extensiones de navegador e instale una en su navegador. Puede examinar sus archivos de formas interesantes. ¿Qué descubres?
## [Post-lecture prueba](.github/post-lecture-quiz.md)
## Revisión y autoestudio
En esta lección aprendió un poco sobre la historia del navegador web; Aproveche esta oportunidad para aprender cómo los inventores de la World Wide Web imaginaron su uso leyendo más sobre su historia. Algunos sitios útiles incluyen:
[La historia de los navegadores web](https://www.mozilla.org/en-US/firefox/browsers/browser-history/)
[Historia de la Web](https://webfoundation.org/about/vision/history-of-the-web/)
[Una entrevista con Tim Berners-Lee](https://www.theguardian.com/technology/2019/mar/12/tim-berners-lee-on-30-years-of-the-web-if-we-sueñe-un-poco-podemos-conseguir-la-web-que-queremos)
**Tarea**: [Cambia el estilo de tu extensión](assignment.md)

@ -0,0 +1,11 @@
# Cambia el estilo de tu extensión
## Instrucciones
El código base para esta extensión viene completo con estilos, pero no es necesario que los uses; haga suya su extensión al cambiarle el estilo editando su archivo css.
## Rúbrica
| Criterios | Ejemplar | Adecuado | Necesita mejorar |
| -------- | -------------------------------------------- | --------------------- | ----------------- |
| | El código se envía con nuevos estilos funcionales | El estilo está incompleto | Los estilos tienen errores |

@ -0,0 +1,16 @@
*Complete this quiz by checking one answer per question.*
1. LocalStorage is cleared every time you close the browser window
- [ ] true
- [ ] false
2. The main browser window controls a browser's extension's LocalStorage
- [ ] true
- [ ] false
3. REST in an API context stands for
- [ ] Representational State Transfer
- [ ] Returning State Tasks
- [ ] Rendering State To Browser

@ -0,0 +1,20 @@
*A warm-up quiz about the browser*
Complete this quiz in class
1. APIs stand for
- [ ] Application Programming Interfaces
- [ ] A Programming Inference
- [ ] Anti Proven Intentions
2. Use an API to interact with
- [ ] Another web-connected asset
- [ ] A database
- [ ] Either of the above
3. Anyone can create an API
- [ ] true
- [ ] false

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 WebDev-For-Beginners
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,218 @@
# Browser Extension Project Part 2: Call an API, use Local Storage
## [Pre-lecture quiz](.github/pre-lecture-quiz.md)
### Introduction:
In this lesson, you'll call an API by submitting your browser extension's form and display the results in your browser extension. In addition, you'll learn about how you can store data in your browser's local storage for future reference and use.
✅ Follow the numbered segments in the appropriate files to know where to place your code
### 1. Set up the elements to manipulate in the extension:
By this time you have built the HTML for the form and results `<div>` for your browser extension. From now on, you'll need to work in the `/src/index.js` file and building your extension bit by bit. Refer to the [previous lesson](../about-browsers/README.md) on getting your project set up and on the build process.
Working in your `index.js` file, start by creating some `const` variables to hold the values associated with various fields:
```JavaScript
// form fields
const form = document.querySelector('.form-data');
const region = document.querySelector('.region-name');
const apiKey = document.querySelector('.api-key');
// results
const errors = document.querySelector('.errors');
const loading = document.querySelector('.loading');
const results = document.querySelector('.result-container');
const usage = document.querySelector('.carbon-usage');
const fossilfuel = document.querySelector('.fossil-fuel');
const myregion = document.querySelector('.my-region');
const clearBtn = document.querySelector('.clear-btn');
```
All of these fields are referenced by their css class, as you set it up in the HTML in the previous lesson.
### 2. Add listeners
Next, add event listeners to the form and the clear button that resets the form, so that if a user submits the form or clicks that reset button, something will happen, and add the call to initialize the app at the bottom of the file:
```JavaScript
form.addEventListener('submit', (e) => handleSubmit(e));
clearBtn.addEventListener('click', (e) => reset(e));
init();
```
✅ Notice the shorthand used to listen for a submit or click event, and how the event it is passed to the handleSubmit or reset functions. Can you write the equivalent of this shorthand in a longer format? Which do you prefer?
### 3. Build out the init() function and the reset() function:
Now you are going to build the function that initializes the extension, which is called init():
```JavaScript
function init() {
//if anything is in localStorage, pick it up
const storedApiKey = localStorage.getItem('apiKey');
const storedRegion = localStorage.getItem('regionName');
//set icon to be generic green
//todo
if (storedApiKey === null || storedRegion === null) {
//if we don't have the keys, show the form
form.style.display = 'block';
results.style.display = 'none';
loading.style.display = 'none';
clearBtn.style.display = 'none';
errors.textContent = '';
} else {
//if we have saved keys/regions in localStorage, show results when they load
displayCarbonUsage(storedApiKey, storedRegion);
results.style.display = 'none';
form.style.display = 'none';
clearBtn.style.display = 'block';
}
};
function reset(e) {
e.preventDefault();
//clear local storage for region only
localStorage.removeItem('regionName');
init();
}
```
In this function, there is some interesting logic. Reading through it, can you see what happens?
- two `const` are set up to check if the user has stored an APIKey and region code in local storage.
- if either of those is null, show the form by changing its style to display as 'block'
- hide the results, loading, and clearBtn and set any error text to an empty string
- if there exists a key and region, start a routine to:
- call the API to get carbon usage data
- hide the results area
- hide the form
- show the reset button
Before moving on, it's useful to learn about a very important concept available in browsers: [LocalStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage). LocalStorage is a useful way to store strings in the browser as a `key-value` pair. This type of web storage can be manipulated by JavaScript to manage data in the browser. LocalStorage does not expire, while SessionStorage, another kind of web storage, is cleared when the browser is closed. The various types of storage have pros and cons to their usage.
> Note - your browser extension has its own local storage; the main browser window is a different instance and behaves separately.
You set your APIKey to have a string value, for example, and you can see that it is set on Edge by "inspecting" a web page (you can right-click a browser to inspect) and going to the Applications tab to see the storage.
![Local storage pane](images/localstorage.png)
✅ Think about situations where you would NOT want to store some data in LocalStorage. In general, placing API Keys in LocalStorage is a bad idea! Can you see why? In our case, since our app is purely for learning and will not be deployed to an app store, we will use this method.
Notice that you use the Web API to manipulate LocalStorage, either by using `getItem()`, `setItem()` or `removeItem()`. It's widely supported across browsers.
Before building the `displayCarbonUsage()` function that is called in `init()`, let's build the functionality to handle the initial form submission.
### 4. Handle the form submission
Create a function called `handleSubmit` that accepts an event argument `(e)`. Stop the event from propagating (in this case, we want to stop the browser from refreshing) and call a new function, `setUpUser`, passing in the arguments `apiKey.value` and `region.value`. In this way, you use the two values that are brought in via the initial form when the appropriate fields are populated.
```JavaScript
function handleSubmit(e) {
e.preventDefault();
setUpUser(apiKey.value, region.value);
}
```
✅ Refresh your memory - the HTML you set up in the last lesson has two input fields whose `values` are captured via the `const` you set up at the top of the file, and they are both `required` so the browser stops users from inputting null values.
### 5. Set up the user
Moving on to the `setUpUser` function, here is where you set local storage values for apiKey and regionName. Add a new function:
```JavaScript
function setUpUser(apiKey, regionName) {
localStorage.setItem('apiKey', apiKey);
localStorage.setItem('regionName', regionName);
loading.style.display = 'block';
errors.textContent = '';
clearBtn.style.display = 'block';
//make initial call
displayCarbonUsage(apiKey, regionName);
}
```
This function sets a loading message to show while the API is called. At this point, you have arrived at creating the most important function of this browser extension!
### 6. Display Carbon Usage
Finally it's time to query the API!
Before going further, we should discuss APIs. APIs, or [Application Programming Interfaces](https://www.webopedia.com/TERM/A/API.html), are a critical element of a web developer's toolbox. They provide standard ways for programs to interact and interface with each other. For example, if you are building a web site that needs to query a database, someone might have created an API for you to use. While there are many types of APIs, one of the most popular is a [REST API](https://www.smashingmagazine.com/2018/01/understanding-using-rest-api/).
✅ The term 'REST' stands for 'Representational State Transfer' and features using variously-configured URLs to fetch data. Do a little research on the various types of APIs available to developers. What format appeals to you?
There are important things to note about this function. First notice the [`async` keyword](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function). Writing your functions so that they run asynchronously means that they wait for an action, such as data being returned, to be completed before continuing.
Here's a quick video about `async`:
[![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")
Create a new function to query the C02Signal API:
```JavaScript
import axios from '../node_modules/axios';
async function displayCarbonUsage(apiKey, region) {
try {
await axios
.get('https://api.co2signal.com/v1/latest', {
params: {
countryCode: region,
},
headers: {
'auth-token': apiKey,
},
})
.then((response) => {
let CO2 = Math.floor(response.data.data.carbonIntensity);
//calculateColor(CO2);
loading.style.display = 'none';
form.style.display = 'none';
myregion.textContent = region;
usage.textContent =
Math.round(response.data.data.carbonIntensity) + ' grams (grams C02 emitted per kilowatt hour)';
fossilfuel.textContent =
response.data.data.fossilFuelPercentage.toFixed(2) +
'% (percentage of fossil fuels used to generate electricity)';
results.style.display = 'block';
});
} catch (error) {
console.log(error);
loading.style.display = 'none';
results.style.display = 'none';
errors.textContent = 'Sorry, we have no data for the region you have requested.';
}
}
```
This is a big function. What's going on here?
- following best practices, you use an `async` keyword to make this function behave asyncronously. The function contains a `try/catch` block as it will return a promise when the API returns data. Because you don't have control over the speed that the API will respond (it may not respond at all!), you need to handle this uncertainty by call it asyncronously.
- you're querying the co2signal API to get your region's data, using your API Key. To use that key, you have to use a type of authentication in your header parameters.
- once the API responds, you assign various elements of its response data to the parts of your screen you set up to show this data.
- if there's an error, or if there is no result, you show an error message.
✅ Using asyncronous programming patterns is another very useful tool in your toolbox. Read [about the various ways](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) you can configure this type of code.
Congratulations! If you build your extension (`npm run build`) and refresh it in your extensions pane, you have a working extension! The only thing that isn't working is the icon, and you'll fix that in the next lesson.
---
## 🚀 Challenge
We've discussed several types of API so far in these lessons. Choose a web API and research in depth what it offers. For example, take a look at APIs available within browsers such as the [HTML Drag and Drop API](https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API). What makes a great API in your opinion?
## [Post-lecture quiz](.github/post-lecture-quiz.md)
## Review & Self Study
You learned about LocalStorage and APIs in this lesson, both very useful for the professional web developer. Can you think how these two things work together? Think about how you would architect a web site that would store items to be used by an API.
## Assignment
[Adopt an API](assignment.md)

@ -0,0 +1,11 @@
# Adopt an API
## Instructions
APIs can be very fun to play with. Here is a [list of many free ones](https://github.com/public-apis/public-apis). Pick and API, and build a browser extension that solves a problem. It can be as small a problem of not having enough pet pictures (so, try the [dog CEO API](https://dog.ceo/dog-api/)) or something bigger - have fun!
## Rubric
| Criteria | Exemplary | Adequate | Needs Improvement |
| -------- | -------------------------------------------------------------------------- | ---------------------------------------- | ----------------------- |
| | A complete browser extension is submitted using an API from the above list | A partial browser extension is submitted | The submission has bugs |

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

@ -0,0 +1,212 @@
# Proyecto de extensión del navegador, parte 2: llamar a una API, usar almacenamiento local
## [Pre-lecture prueba](.github/pre-lecture-quiz.md)
### Introducción:
En esta lección, llamará a una API enviando el formulario de la extensión de su navegador y mostrará los resultados en la extensión de su navegador. Además, aprenderá cómo puede almacenar datos en el almacenamiento local de su navegador para referencia y uso futuros.
✅ Siga los segmentos numerados en los archivos correspondientes para saber dónde colocar su código
### 1. Configure los elementos para manipular en la extensión:
En este momento, ha creado el HTML para el formulario y los resultados `<div>` para la extensión de su navegador. De ahora en adelante, necesitará trabajar en el archivo `/src/index.js` y construir su extensión poco a poco. Consulte la [lección anterior](../about-browsers/ README.md) sobre cómo configurar su proyecto y el proceso de compilación.
Trabajando en su archivo `index.js`, comience creando algunas variables `const` para contener los valores asociados con varios campos:
```JavaScript
// form fields
const form = document.querySelector('.form-data');
const region = document.querySelector('.region-name');
const apiKey = document.querySelector('.api-key');
// results
const errors = document.querySelector('.errors');
const loading = document.querySelector('.loading');
const results = document.querySelector('.result-container');
const usage = document.querySelector('.carbon-usage');
const fossilfuel = document.querySelector('.fossil-fuel');
const myregion = document.querySelector('.my-region');
const clearBtn = document.querySelector('.clear-btn');
```
Todos estos campos están referenciados por su clase css, como lo configuró en el HTML en la lección anterior.
### 2. Agregar oyentes
A continuación, agregue detectores de eventos al formulario y el botón de borrar que restablece el formulario, de modo que si un usuario envía el formulario o hace clic en ese botón de restablecimiento, algo sucederá y agregue la llamada para inicializar la aplicación en la parte inferior del archivo:
```JavaScript
form.addEventListener('submit', (e) => handleSubmit(e));
clearBtn.addEventListener('click', (e) => reset(e));
init();
```
✅ Observe la abreviatura que se usa para escuchar un evento de envío o clic, y cómo el evento se pasa a las funciones handleSubmit o reset. ¿Puedes escribir el equivalente de esta abreviatura en un formato más largo? ¿Cual prefieres?
### 3. Desarrolle la init() y reset() función:
Ahora vas a construir la función que inicializa la extensión, que se llama `init()`:
```JavaScript
function init() {
//si hay algo en localStorage, recójalo
const storedApiKey = localStorage.getItem('apiKey');
const storedRegion = localStorage.getItem('regionName');
//establecer el icono en verde genérico
//todo
if (storedApiKey === null || storedRegion === null) {
//si no tenemos las claves, mostrar el formulario
form.style.display = 'block';
results.style.display = 'none';
loading.style.display = 'none';
clearBtn.style.display = 'none';
errors.textContent = '';
} else {
//si hemos guardado claves / regiones en localStorage, mostrar los resultados cuando se cargan
displayCarbonUsage(storedApiKey, storedRegion);
results.style.display = 'none';
form.style.display = 'none';
clearBtn.style.display = 'block';
}
};
function reset(e) {
e.preventDefault();
//borrar almacenamiento local solo para la región
localStorage.removeItem('regionName');
init();
}
```
En esta función, hay una lógica interesante. Al leerlo, ¿puedes ver lo que sucede?
- Se configuran dos `const` para verificar si el usuario ha almacenado una clave API y un código de región en el almacenamiento local.
- si alguno de ellos es nulo, muestre el formulario cambiando su estilo para que se muestre como 'bloque'
- ocultar los resultados, cargar y borrarBtn y establecer cualquier texto de error en una cadena vacía
- si existe una clave y una región, inicie una rutina para:
- llamar a la API para obtener datos de uso de carbono
- ocultar el área de resultados
- ocultar el formulario
- mostrar el botón de reinicio
Antes de continuar, es útil conocer un concepto muy importante disponible en los navegadores: [LocalStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage). LocalStorage es una forma útil de almacenar cadenas en el navegador como un par "clave-valor". Este tipo de almacenamiento web puede ser manipulado por JavaScript para administrar datos en el navegador. LocalStorage no caduca, mientras que SessionStorage, otro tipo de almacenamiento web, se borra cuando se cierra el navegador. Los distintos tipos de almacenamiento tienen ventajas y desventajas para su uso.
> Nota: la extensión de su navegador tiene su propio almacenamiento local; la ventana principal del navegador es una instancia diferente y se comporta por separado.
Configura su APIKey para que tenga un valor de cadena, por ejemplo, y puede ver que está configurado en Edge "inspeccionando" una página web (puede hacer clic con el botón derecho en un navegador para inspeccionar) y yendo a la pestaña Aplicaciones para ver el almacenamiento.
![Panel de almacenamiento local](images/localstorage.png)
✅ Piense en situaciones en las que NO le gustaría almacenar algunos datos en LocalStorage. En general, colocar claves API en LocalStorage es una mala idea. ¿Puedes ver por qué? En nuestro caso, dado que nuestra aplicación es puramente para aprender y no se implementará en una tienda de aplicaciones, usaremos este método.
Tenga en cuenta que utiliza la API web para manipular LocalStorage, ya sea utilizando `getItem()`, `setItem()` o `removeItem()`. Es ampliamente compatible con todos los navegadores.
Antes de construir la función `displayCarbonUsage()` que se llama en `init()`, construyamos la funcionalidad para manejar el envío inicial del formulario.
### 4. Manejar el envío del formulario
Cree una función llamada `handleSubmit` que acepte un argumento de evento `(e)`. Detenga la propagación del evento (en este caso, queremos que el navegador no se actualice) y llame a una nueva función, `setUpUser`, pasando los argumentos `apiKey.value` y `region.value`. De esta manera, utiliza los dos valores que se introducen a través del formulario inicial cuando se completan los campos correspondientes.
```JavaScript
function handleSubmit(e) {
e.preventDefault();
setUpUser(apiKey.value, region.value);
}
```
✅ Refresque su memoria: el HTML que configuró en la última lección tiene dos campos de entrada cuyos `values` se capturan mediante la `const` que configuró en la parte superior del archivo, y ambos son `required` para que el navegador detenga a los usuarios de ingresar valores nulos.
### 5. Configurar el usuario
Pasando a la función `setUpUser`, aquí es donde configura los valores de almacenamiento local para apiKey y regionName. Agrega una nueva función:
```JavaScript
function setUpUser(apiKey, regionName) {
localStorage.setItem('apiKey', apiKey);
localStorage.setItem('regionName', regionName);
loading.style.display = 'block';
errors.textContent = '';
clearBtn.style.display = 'block';
//make initial call
displayCarbonUsage(apiKey, regionName);
}
```
Esta función establece un mensaje de carga para mostrar mientras se llama a la API. ¡En este punto, ha llegado a crear la función más importante de esta extensión de navegador!
### 6. Visualización del uso de carbono
¡Finalmente es hora de consultar la API!
Antes de continuar, deberíamos discutir las API. Las API, o [Interfaces de programación de aplicaciones](https://www.webopedia.com/TERM/A/API.html), son un elemento fundamental de la caja de herramientas de un desarrollador web. Proporcionan formas estándar para que los programas interactúen e interactúen entre sí. Por ejemplo, si está creando un sitio web que necesita consultar una base de datos, alguien podría haber creado una API para que la use. Si bien hay muchos tipos de API, una de las más populares es una [API REST](https://www.smashingmagazine.com/2018/01/understanding-using-rest-api/).
✅ El término 'REST' significa 'Transferencia de estado representacional' y características que utilizan URL configuradas de diversas formas para obtener datos. Investigue un poco sobre los distintos tipos de API disponibles para los desarrolladores. ¿Qué formato te atrae?
Hay cosas importantes a tener en cuenta sobre esta función. Primero observe la palabra clave `async`. Escribir sus funciones para que se ejecuten de forma asincrónica significa que
Cree una nueva función para consultar la API C02Signal:
```JavaScript
import axios from '../node_modules/axios';
async function displayCarbonUsage(apiKey, region) {
try {
await axios
.get('https://api.co2signal.com/v1/latest', {
params: {
countryCode: region,
},
headers: {
'auth-token': apiKey,
},
})
.then((response) => {
let CO2 = Math.floor(response.data.data.carbonIntensity);
//calculateColor(CO2);
loading.style.display = 'none';
form.style.display = 'none';
myregion.textContent = region;
usage.textContent =
Math.round(response.data.data.carbonIntensity) + ' grams (grams C02 emitted per kilowatt hour)';
fossilfuel.textContent =
response.data.data.fossilFuelPercentage.toFixed(2) +
'% (percentage of fossil fuels used to generate electricity)';
results.style.display = 'block';
});
} catch (error) {
console.log(error);
loading.style.display = 'none';
results.style.display = 'none';
errors.textContent = 'Sorry, we have no data for the region you have requested.';
}
}
```
Esta es una gran función. ¿Que está pasando aqui?
- siguiendo las mejores prácticas, utiliza una palabra clave `async` para hacer que esta función se comporte de forma asíncrona. La función contiene un bloque `try / catch` ya que devolverá una promesa cuando la API devuelva datos. Debido a que no tiene control sobre la velocidad a la que responderá la API (¡es posible que no responda en absoluto!), Debe manejar esta incertidumbre llamándola de forma asincrónica.
- está consultando la API co2signal para obtener los datos de su región, utilizando su clave API. Para usar esa clave, debe usar un tipo de autenticación en los parámetros de su encabezado.
- una vez que la API responde, asigna varios elementos de sus datos de respuesta a las partes de su pantalla que configura para mostrar estos datos.
- si hay un error o si no hay ningún resultado, muestra un mensaje de error.
✅ El uso de patrones de programación asíncronos es otra herramienta muy útil en su caja de herramientas. Lea [acerca de las diversas formas](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) puede configurar este tipo de código.
¡Felicidades! Si construye su extensión (`npm run build`) y la actualiza en su panel de extensiones, ¡tiene una extensión que funciona! Lo único que no funciona es el ícono, y lo solucionará en la próxima lección.
---
🚀 Desafío: hemos discutido varios tipos de API hasta ahora en estas lecciones. Elija una API web e investigue en profundidad lo que ofrece. Por ejemplo, eche un vistazo a las API disponibles en los navegadores, como la [API HTML de arrastrar y soltar] (https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API). En tu opinión, ¿qué hace que una API sea excelente?
## [Post-lecture prueba](.github/post-lecture-quiz.md)
## Revisión y autoestudio
Aprendió sobre LocalStorage y las API en esta lección, ambos muy útiles para el desarrollador web profesional. ¿Puedes pensar en cómo funcionan estas dos cosas juntas? Piense en cómo diseñaría un sitio web que almacenaría elementos para ser utilizados por una API.
**Tarea**: [Adopte una API](assignment.md)

@ -0,0 +1,11 @@
# Adopte una API
## Instrucciones
Puede ser muy divertido jugar con las API. Aquí hay una [lista de muchos gratuitos](https://github.com/public-apis/public-apis). Elija una API y cree una extensión de navegador que resuelva un problema. Puede ser un problema tan pequeño el no tener suficientes imágenes de mascotas (por lo tanto, pruebe la [API de CEO para perros](https://dog.ceo/dog-api/)) o algo más grande: ¡diviértase!
## Rúbrica
| Criterios | Ejemplar | Adecuado | Necesita mejorar |
| -------- | -------------------------------------------------- ------------------------ | ---------------------------------------- | ----------------------- |
| | Se envía una extensión de navegador completa utilizando una API de la lista anterior | Se envía una extensión de navegador parcial | La presentación tiene errores |

@ -0,0 +1,18 @@
*Complete this quiz by checking one answer per question.*
1. To get a better view of your site's performance, clear its cache and reload in the profiler
- [ ] true
- [ ] false
2. Browser extensions are inherently performant
- [ ] true
- [ ] false
3. Analyze the following for performance bottlenecks
- [ ] DOM traversals
- [ ] JavaScript optimizations
- [ ] Asset management
- [ ] All the above

@ -0,0 +1,18 @@
*Complete this quiz in class.*
1. Test the performance of your app
- [ ] Using the browser's tools
- [ ] Using a separate software package
- [ ] Manually
2. The 'performance' of a web site is an analysis of
- [ ] How fast it loads
- [ ] How fast the code on it runs
- [ ] Both of the above
3. Overall, the 'weight' of web pages over the past few years has gotten
- [ ] lighter
- [ ] heavier
- [ ] stayed the same

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 WebDev-For-Beginners
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,157 @@
# Browser Extension Project Part 3: Learn about Background Tasks and Performance
## [Pre-lecture quiz](.github/pre-lecture-quiz.md)
### Introduction:
In the last two lessons of this module, you learned how to build a form and display area for data fetched from an API. It's a very standard way of creating web presences on the web. You even learned how to handle fetching data asyncronously. Your browser extension is very nearly complete.
It remains to manage some background tasks, including refreshing the color of the extension's icon, so this is a great time to talk about how the browser manages this kind of task. Let's think about these browser tasks in the context of the performance of your web assets as you build them.
## Web Performance Basics
> "Website performance is about two things: how fast the page loads, and how fast the code on it runs." -- [Zack Grossbart](https://www.smashingmagazine.com/2012/06/javascript-profiling-chrome-developer-tools/)
The topic of how to make your web sites blazingly fast on all kinds of devices, for all kinds of users, in all kinds of situations, is unsurprisingly vast. Here are some points to keep in mind as you build either a standard web project or a browser extension.
The first thing you need to do to ensure that your site is running efficiently is to gather data about its performance. The first place to do this is in the developer tools of your web browser. In Edge, you can select the three dots on the top right of the browser, then navigate to More Tools > Developer Tools and open the Performance tab.
The Performance tab contains a Profiling tool. Open a web site (try, for example, https://www.microsoft.com) and click the 'Record' button, then refresh the site. Stop the recording at any time, and you will be able to see the routines that are generated to 'script', 'render', and 'paint' the site:
![Edge profiler](./images/profiler.png)
✅ Visit the [Microsoft Documentation](https://docs.microsoft.com/en-us/microsoft-edge/devtools-guide/performance) on the Performance panel in Edge
> Tip: to get a true reading of your web site's startup time, clear your browser's cache
Select elements of the profile timeline to zoom in on events that happen while your page loads.
Get a snapshot of your page's performance by selecting a part of the profile timeline and looking at the summary pane:
![Edge profiler snapshot](./images/snapshot.png)
Check the Event Log pane to see if any event took longer than 15 ms:
![Edge event log](./images/log.png)
✅ Get to know your profiler! Open the developer tools on this site and see if there are any bottlenecks. What's the slowest-loading asset? The fastest?
## Profiling checks
In general there are some "problem areas" that every web developer should watch for when building a site, so as to avoid nasty surprises when it's time to deploy to production.
**Asset sizes**: The web has gotten 'heavier', and thus slower, over the past few years. Some of this weight has to do with the use of images.
✅ Look through the [Internet Archive](https://httparchive.org/reports/page-weight) for a historical view of page weight and more.
A good practice is to ensure that your images are optimized, delivered at the right size and resolution for your users.
**DOM traversals**: The browser has to build its Document Object Model based on the code you write, so it's in the interest of good page performance to keep your tags minimal, only using and styling what the page needs. To this point, excess CSS associated with a page could be optimized; styles that need to be used only on one page don't need to be included in the main style sheet, for example.
**JavaScript**: Every JavaScript developer should watch for 'render-blocking' scripts that must be loaded before the rest of the DOM can be traversed and painted to the browser. Consider using `defer` with your inline scripts (as is done in the Terrarium module).
✅ Try some sites on a [Site Speed Test website](https://www.webpagetest.org/) to learn more about the common checks that are done to determine site performance.
Now that you have an idea on how the browser renders the assets you send to it, let's look at the last few things you need to do to complete your extension:
### Create a function to calculate color
Working in `/src/index.js`, add a function called `calculateColor()` after the series of `const` variables you set to gain access to the DOM:
```JavaScript
function calculateColor(value) {
let co2Scale = [0, 150, 600, 750, 800];
let colors = ['#2AA364', '#F5EB4D', '#9E4229', '#381D02', '#381D02'];
let closestNum = co2Scale.sort((a, b) => {
return Math.abs(a - value) - Math.abs(b - value);
})[0];
console.log(value + ' is closest to ' + closestNum);
let num = (element) => element > closestNum;
let scaleIndex = co2Scale.findIndex(num);
let closestColor = colors[scaleIndex];
console.log(scaleIndex, closestColor);
chrome.runtime.sendMessage({ action: 'updateIcon', value: { color: closestColor } });
}
```
What's going on here? You pass in a value (the carbon intensity) from the API call you completed in the last lesson, and then you calculate how close its value is to the index presented in colors array. Then you send that closest color value over to the chrome runtime.
The chrome.runtime has [an API](https://developer.chrome.com/extensions/runtime) that handles all kinds of background tasks, and your extension is leveraging that:
> "Use the chrome.runtime API to retrieve the background page, return details about the manifest, and listen for and respond to events in the app or extension lifecycle. You can also use this API to convert the relative path of URLs to fully-qualified URLs."
✅ If you're developing this browser extension for Edge, it might surprise you that you're using a chrome API. The newer Edge browser versions run on the Chromium browser engine, so you can leverage these tools.
> Note, if you want to profile a browser extension, launch the dev tools from within the extension itself, as it is its own separate browser instance.
### Set a default icon color
Now, in the `init()` function, set the icon to be generic green to start by again calling chrome's `updateIcon` action:
```JavaScript
chrome.runtime.sendMessage({
action: 'updateIcon',
value: {
color: 'green',
},
});
```
### Call the function, execute the call
Next, call that function you just created by adding it to the promise returned by the C02Signal API:
```JavaScript
//let CO2...
calculateColor(CO2);
```
And finally, in `/dist/background.js`, add the listener for these background action calls:
```JavaScript
chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
if (msg.action === 'updateIcon') {
chrome.browserAction.setIcon({ imageData: drawIcon(msg.value) });
}
});
//borrowed from energy lollipop extension, nice feature!
function drawIcon(value) {
let canvas = document.createElement('canvas');
let context = canvas.getContext('2d');
context.beginPath();
context.fillStyle = value.color;
context.arc(100, 100, 50, 0, 2 * Math.PI);
context.fill();
return context.getImageData(50, 50, 100, 100);
}
```
In this code, you are adding a listener for any messages coming to the backend task manager. If it's called 'updateIcon', then the next code is run, to draw an icon of the proper color using the Canvas API.
✅ You'll learn more about the Canvas API in the [Space Game lessons](../../space-game/drawing-to-canvas/README.md).
Now, rebuild your extension (`npm run build`), refresh and launch your extension, and watch the color change. Is it a good time to run an errand or wash the dishes? Now you know!
Congratulations, you've built a useful browser extension and learned more about how the browser works and how to profile its performance.
---
## 🚀 Challenge
Investigate some open source web sites have been around a long time ago, and, based on their GitHub history, see if you can determine how they were optimized over the years for performance, if at all. What is the most common pain point?
## [Post-lecture quiz](.github/post-lecture-quiz.md)
## Review & Self Study
Consider signing up for a [performance newsletter](https://perf.email/)
Investigate some of the ways that browsers gauge web performance by looking through the performance tabs in their web tools. Do you find any major differences?
## Assignment
[Analyze a site for performance](assignment.md)

@ -0,0 +1,9 @@
# Analyze a site for performance
Provide a detailed report of a web site, showing areas where performance is problematic. Analyze why the site is slow and what you could do to speed it up. Don't rely only on the browser tools, but do some research on other tools that can help your report
## Rubric
| Criteria | Exemplary | Adequate | Needs Improvement |
| -------- | ---------------------------------------------------------------------------------------------------------- | --------------------------- | ----------------------------- |
| | A report is presented with details drawn not only from browser tools but from 3rd party tools if available | A basic report is presented | A minimal report is presented |

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 830 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

@ -0,0 +1,153 @@
# Proyecto de extensión del navegador, parte 3: Más información sobre el rendimiento y las tareas en segundo plano
## [Pre-lecture prueba](.github/pre-lecture-quiz.md)
### Introducción:
En las dos últimas lecciones de este módulo, aprendió cómo crear un formulario y un área de visualización para los datos obtenidos de una API. Es una forma muy estándar de crear presencias web en la web. Incluso aprendió a manejar la obtención de datos de forma asincrónica. La extensión de su navegador está casi completa.
Queda por administrar algunas tareas en segundo plano, incluida la actualización del color del icono de la extensión, por lo que este es un buen momento para hablar sobre cómo el navegador administra este tipo de tareas. Pensemos en estas tareas del navegador en el contexto del rendimiento de sus activos web a medida que los crea.
## Conceptos básicos sobre el rendimiento web
> "El rendimiento del sitio web se trata de dos cosas: qué tan rápido se carga la página y qué tan rápido se ejecuta el código". - [Zack Grossbart](https://www.smashingmagazine.com/2012/06/javascript-profiling-chrome-developer-tools/)
El tema de cómo hacer que sus sitios web sean increíblemente rápidos en todo tipo de dispositivos, para todo tipo de usuarios, en todo tipo de situaciones, es sorprendentemente vasto. Estos son algunos puntos que debe tener en cuenta al crear un proyecto web estándar o una extensión de navegador.
Lo primero que debe hacer para asegurarse de que su sitio funcione de manera eficiente es recopilar datos sobre su rendimiento. El lugar para hacer esto es en las herramientas de desarrollo de su navegador web. En Edge, puede seleccionar los tres puntos en la parte superior derecha del navegador, luego navegar a Más herramientas> Herramientas de desarrollo y abrir la pestaña Rendimiento.
La pestaña Rendimiento contiene una herramienta de creación de perfiles. Abra un sitio web (pruebe, por ejemplo, https://www.microsoft.com) y haga clic en el botón 'Grabar', luego actualice el sitio. Detenga la grabación en cualquier momento y podrá ver las rutinas que se generan para 'escribir', 'renderizar' y 'pintar' el sitio:
![Edge profiler](./images/profiler.png)
✅ Visite la [Documentación de Microsoft](https://docs.microsoft.com/en-us/microsoft-edge/devtools-guide/performance) en el panel Rendimiento en Edge
> Consejo: para obtener una lectura real de la hora de inicio de su sitio web, borre la memoria caché de su navegador
Seleccione elementos de la línea de tiempo del perfil para ampliar los eventos que suceden mientras se carga su página.
Obtenga una instantánea del rendimiento de su página seleccionando una parte de la línea de tiempo del perfil y mirando el panel de resumen:
![Instantánea de Edge Profiler](./images/snapshot.png)
Compruebe el panel Registro de eventos para ver si algún evento tardó más de 15 ms:
![Registro de eventos de Edge](./images/log.png)
✅ ¡Conoce a tu perfilador! Abra las herramientas de desarrollo en este sitio y vea si hay cuellos de botella. ¿Cuál es el activo de carga más lenta? ¿El más rápido?
## Comprobaciones de perfiles
En general, hay algunas "áreas problemáticas" que todo desarrollador web debe tener en cuenta al crear un sitio, para evitar sorpresas desagradables cuando llegue el momento de implementarlo en producción.
**Tamaños de activos**: la Web se ha vuelto 'más pesada' y, por lo tanto, más lenta en los últimos años. Parte de este peso tiene que ver con el uso de imágenes.
✅ Busque en el [Archivo de Internet](https://httparchive.org/reports/page-weight) una vista histórica del peso de la página y más.
Una buena práctica es asegurarse de que sus imágenes estén optimizadas, entregadas con el tamaño y la resolución adecuados para sus usuarios.
**Recorridos de DOM**: el navegador tiene que construir su Modelo de Objetos de Documento basado en el código que usted escribe, por lo que es de interés para un buen rendimiento de la página mantener sus etiquetas al mínimo, solo usando y estilizando lo que la página necesita. Hasta este punto, se podría optimizar el exceso de CSS asociado con una página; los estilos que deben usarse solo en una página no necesitan incluirse en la hoja de estilo principal, por ejemplo.
**JavaScript**: Todo desarrollador de JavaScript debe estar atento a los scripts de 'bloqueo de renderizado' que deben cargarse antes de que el resto del DOM pueda atravesarse y pintarse en el navegador. Considere usar "diferir" con sus scripts en línea (como se hace en el módulo Terrarium).
✅ Pruebe algunos sitios en un [sitio web de prueba de velocidad del sitio](https://www.webpagetest.org/) para obtener más información sobre las comprobaciones comunes que se realizan para determinar el rendimiento del sitio.
Ahora que tiene una idea de cómo el navegador representa los recursos que le envía, veamos las últimas cosas que debe hacer para completar su extensión:
### Crea una función para calcular el color
Trabajando en `/ src / index.js`, agregue una función llamada` calculateColor () `después de la serie de variables` const` que estableció para obtener acceso al DOM:
```JavaScript
function calculateColor(value) {
let co2Scale = [0, 150, 600, 750, 800];
let colors = ['#2AA364', '#F5EB4D', '#9E4229', '#381D02', '#381D02'];
let closestNum = co2Scale.sort((a, b) => {
return Math.abs(a - value) - Math.abs(b - value);
})[0];
console.log(value + ' is closest to ' + closestNum);
let num = (element) => element > closestNum;
let scaleIndex = co2Scale.findIndex(num);
let closestColor = colors[scaleIndex];
console.log(scaleIndex, closestColor);
chrome.runtime.sendMessage({ action: 'updateIcon', value: { color: closestColor } });
}
```
¿Que está pasando aqui? Pasa un valor (la intensidad de carbono) de la llamada API que completó en la última lección, y luego calcula qué tan cerca está su valor del índice presentado en la matriz de colores. Luego, envía el valor de color más cercano al tiempo de ejecución de Chrome.
Chrome.runtime tiene [una API](https://developer.chrome.com/extensions/runtime) que maneja todo tipo de tareas en segundo plano, y tu extensión aprovecha eso:
> "Utilice la API chrome.runtime para recuperar la página de fondo, devolver detalles sobre el manifiesto y escuchar y responder a eventos en el ciclo de vida de la aplicación o extensión. También puede utilizar esta API para convertir la ruta relativa de las URL a URL calificadas".
✅ Si está desarrollando esta extensión de navegador para Edge, puede que le sorprenda que esté utilizando una API de Chrome. Las versiones más recientes del navegador Edge se ejecutan en el motor del navegador Chromium, por lo que puede aprovechar estas herramientas.
> Tenga en cuenta que si desea crear un perfil de una extensión de navegador, inicie las herramientas de desarrollo desde la propia extensión, ya que es su propia instancia de navegador independiente.
### Establecer un color de icono predeterminado
Ahora, en la función `init ()`, configure el ícono en verde genérico para comenzar nuevamente llamando a la acción `updateIcon` de Chrome:
```JavaScript
chrome.runtime.sendMessage({
action: 'updateIcon',
value: {
color: 'green',
},
});
```
### Llame a la función, ejecute la llamada
A continuación, llame a la función que acaba de crear agregándola a la promesa devuelta por la API C02Signal:
```JavaScript
//let CO2...
calculateColor(CO2);
```
Y finalmente, en `/dist/background.js`, agregue el oyente para estas llamadas de acción en segundo plano:
```JavaScript
chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
if (msg.action === 'updateIcon') {
chrome.browserAction.setIcon({ imageData: drawIcon(msg.value) });
}
});
//tomado de la extensión Energy Lollipop, ¡buena característica!
function drawIcon(value) {
let canvas = document.createElement('canvas');
let context = canvas.getContext('2d');
context.beginPath();
context.fillStyle = value.color;
context.arc(100, 100, 50, 0, 2 * Math.PI);
context.fill();
return context.getImageData(50, 50, 100, 100);
}
```
En este código, está agregando un oyente para cualquier mensaje que llegue al administrador de tareas de backend. Si se llama 'updateIcon', entonces se ejecuta el siguiente código para dibujar un icono del color adecuado usando la API de Canvas.
✅ Aprenderá más sobre la API Canvas en las [lecciones del juego espacial](../../space-game/drawing-to-canvas/README.md).
Ahora, reconstruya su extensión (`npm run build`), actualice y ejecute su extensión, y observe el cambio de color. ¿Es un buen momento para hacer un mandado o lavar los platos? ¡Ahora lo sabes!
Felicitaciones, ha creado una extensión de navegador útil y ha aprendido más sobre cómo funciona el navegador y cómo perfilar su rendimiento.
🚀 Desafío: Investigue algunos sitios web de código abierto que han existido hace mucho tiempo y, según su historial de GitHub, vea si puede determinar cómo se optimizaron a lo largo de los años para el rendimiento, si es que lo hicieron. ¿Cuál es el punto de dolor más común?
## [Post-lecture prueba] (.github/post-lecture-quiz.md)
## Revisión y autoestudio
Considere suscribirse a un [boletín informativo de rendimiento](https://perf.email/)
Investigue algunas de las formas en que los navegadores miden el rendimiento web consultando las pestañas de rendimiento de sus herramientas web. ¿Encuentra diferencias importantes?
**Tarea**: [Analizar el rendimiento de un sitio](assignment.md)

@ -0,0 +1,9 @@
# Analizar el rendimiento de un sitio
Proporcione un informe detallado de un sitio web, que muestre las áreas donde el rendimiento es problemático. Analice por qué el sitio es lento y qué podría hacer para acelerarlo. No confíe solo en las herramientas del navegador, investigue un poco sobre otras herramientas que pueden ayudar a su informe
## Rúbrica
| Criterios | Ejemplar | Adecuado | Necesita mejorar |
| -------- | -------------------------------------------------- -------------------------------------------------- ------ | --------------------------- | ----------------------------- |
| | Se presenta un informe con detalles extraídos no solo de las herramientas del navegador, sino también de herramientas de terceros, si están disponibles | Se presenta un informe básico | Se presenta un informe mínimo |

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Jen Looper
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,28 @@
# Building a browser extension
Building browser extensions is a fun and interesting way to think about the performance of your apps while building a different type of web asset. This module includes lessons on how browsers work and how to deploy a browser extension, how to build a form, call an API, and use local storage, and how to gauge the performance of your website and improve it.
You'll build a browser extension that works on Edge, Chrome, and Firefox. This extension, which is like a mini web site that is tailored to a very specific task, checks the [C02 Signal API](https://www.co2signal.com) for a given region's electricity usage and carbon intensity, and returns a reading on the region's carbon footprint.
This extension can be called ad hoc by a user once an API key and region code is input into a form to determine local electricity usage and thereby offer data that can influence a user's electricity decisions. For example, it may be preferable to delay running a clothes dryer (a carbon-intense activity) during a period of high electricity usage in your region.
### Topics
1. [About the browser](1-about-browsers/README.md)
2. [Forms and local storage](2-forms-browsers-local-storage/README.md)
3. [Background tasks and performance](3-background-tasks-and-performance/README.md)
### Credits
![a green browser extension](extension-screenshot.png)
## Credits
The idea for this web carbon trigger was offered by Asim Hussain, lead at Microsoft of the Green Cloud Advocacy team and author of the [Green Principles](https://principles.green/). It was originally a [web site project](https://github.com/jlooper/green).
The structure of the browser extension was influenced by [Adebola Adeniran's COVID extension](https://github.com/onedebos/covtension).
The concept behind the 'dot' icon system was suggested by the icon structure of the [Energy Lollipop](https://energylollipop.com/) browser extension for California emissions.
These lessons were written with ♥️ by [Jen Looper](https://www.twitter.com/jenlooper)

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

@ -0,0 +1,28 @@
# Carbon Trigger Browser Extension: Completed Code
Using tmrow's C02 Signal API to track electricity usage, build a browser extension so that you can have a reminder right in your browser about how heavy your region's electricity usage is. Using this extension ad hoc will help you to make judgement calls on your activities based on this information.
![extension screenshot](../extension-screenshot.png)
## Getting Started
You will need to have [npm](https://npmjs.com) installed. Download a copy of this code to a folder on your computer.
Install all the required packages:
```
npm install
```
Build the extension from webpack
```
npm run build
```
To install on Edge, use the 'three dot' menu on the top right corner of the browser to find the Extensions panel. From there, select 'Load Unpacked' to load a new extension. Open the 'dist' folder at the prompt and the extension will load. To use it, you will need an API key for CO2 Signal's API ([get one here via email](https://www.co2signal.com/) - enter your email in the box on this page) and the [code for your region](http://api.electricitymap.org/v3/zones) corresponding to the [Electricity Map](https://www.electricitymap.org/map) (in Boston, for example, I use 'US-NEISO').
![installing](../install-on-edge.png)
Once the API key and region is input into the extension interface, the colored dot in the browser extension bar should change to reflect your region's energy usage and give you a pointer on what energy-heavy activities would be appropriate for you to perform. The concept behind this 'dot' system was given to me by the [Energy Lollipop extension](https://energylollipop.com/) for California emissions.

@ -0,0 +1,17 @@
chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
if (msg.action === 'updateIcon') {
chrome.browserAction.setIcon({ imageData: drawIcon(msg.value) });
}
});
//borrowed from energy lollipop extension, nice feature!
function drawIcon(value) {
let canvas = document.createElement('canvas');
let context = canvas.getContext('2d');
context.beginPath();
context.fillStyle = value.color;
context.arc(100, 100, 50, 0, 2 * Math.PI);
context.fill();
return context.getImageData(50, 50, 100, 100);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>My Carbon Trigger</title>
<link rel="stylesheet" href="./styles.css" />
</head>
<body class="container">
<img alt="plants and people" src="images/plants-people.png" />
<div>
<h1>Welcome to Your Personal Carbon Trigger!</h1>
</div>
<form class="form-data" autocomplete="on">
<div>
<h2>New? Add your Information</h2>
</div>
<div>
<label>Region Name</label>
<input type="text" required class="region-name" />
</div>
<div>
<label>Your API Key from tmrow</label>
<input type="text" required class="api-key" />
</div>
<button class="search-btn">Submit</button>
</form>
<div class="result">
<div class="loading">loading...</div>
<div class="errors"></div>
<div class="data"></div>
<div class="result-container">
<p><strong>Region: </strong><span class="my-region"></span></p>
<p><strong>Carbon Usage: </strong><span class="carbon-usage"></span></p>
<p><strong>Fossil Fuel Percentage: </strong><span class="fossil-fuel"></span></p>
</div>
<button class="clear-btn">Change region</button>
</div>
<script src="main.js"></script>
</body>
</html>

@ -0,0 +1,12 @@
{
"manifest_version": 2,
"name": "My Carbon Trigger",
"version": "0.1.0",
"permissions": ["<all_urls>"],
"background": {
"scripts": ["background.js"]
},
"browser_action": {
"default_popup": "index.html"
}
}

@ -0,0 +1,143 @@
.container {
margin: 1.5em;
text-align: center;
}
.result {
margin-top: 1em;
}
.clear-btn {
text-align: center;
margin: 0 auto;
}
/*basic css*/
/* courtesy of: https://github.com/vladocar/Basic.css */
* {
box-sizing: border-box;
}
:root {
--sans: 1em/1.6 system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell,
Droid Sans, Helvetica Neue, Fira Sans, sans-serif;
--mono: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, 'Courier New', monospace;
--c1: #0074d9;
--c2: #eee;
--c3: #fff;
--c4: #000;
--c5: #fff;
--m1: 8px;
--rc: 8px;
}
@media (prefers-color-scheme: dark) {
:root {
--c2: #333;
--c3: #1e1f20;
--c4: #fff;
}
}
html {
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
/* General settings */
body {
margin: 0;
font: var(--sans);
font-weight: 400;
font-style: normal;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
background-color: var(--c3);
color: var(--c4);
min-width: 300px;
}
img,
iframe {
border: none;
max-width: 100%;
}
a {
color: var(--c1);
text-decoration: none;
}
a:hover {
color: var(--c1);
text-decoration: underline;
}
/* Headlines */
h1,
h2 {
margin: 0.6em 0;
font-weight: normal;
}
h1 {
font-size: 1.5em;
line-height: 1.2;
}
h2 {
font-size: 1.4em;
line-height: 1.2;
}
/* Rounded Corners*/
input,
button,
img {
border-radius: var(--rc);
margin: 5px;
}
/* Forms */
input,
select,
textarea {
font-size: 1em;
color: var(--c4);
background: var(--c2);
border: 0;
padding: 0.6em;
}
label {
font-weight: bold;
text-align: left;
}
button,
input[type='submit'],
input[type='reset'],
input[type='button'] {
-webkit-appearance: none;
font-size: 1em;
display: inline-block;
color: var(--c5);
background: var(--c1);
border: 0;
margin: 4px;
padding: 0.6em;
cursor: pointer;
text-align: center;
}
button:hover,
button:focus,
input:hover,
textarea:hover,
select:hover {
opacity: 0.8;
}

@ -0,0 +1,25 @@
{
"name": "carbon-trigger-extension",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"watch": "webpack --watch",
"build": "webpack"
},
"keywords": [
"chrome extension",
"edge extension",
"carbon usage tracker"
],
"author": "Jen Looper",
"license": "MIT",
"devDependencies": {
"webpack": "^4.44.1",
"webpack-cli": "^3.3.12"
},
"dependencies": {
"axios": "^0.19.2"
}
}

@ -0,0 +1,127 @@
import axios from './node_modules/axios';
// form fields
const form = document.querySelector('.form-data');
const region = document.querySelector('.region-name');
const apiKey = document.querySelector('.api-key');
// results
const errors = document.querySelector('.errors');
const loading = document.querySelector('.loading');
const results = document.querySelector('.result-container');
const usage = document.querySelector('.carbon-usage');
const fossilfuel = document.querySelector('.fossil-fuel');
const myregion = document.querySelector('.my-region');
const clearBtn = document.querySelector('.clear-btn');
calculateColor = async (value) => {
let co2Scale = [0, 150, 600, 750, 800];
let colors = ['#2AA364', '#F5EB4D', '#9E4229', '#381D02', '#381D02'];
let closestNum = co2Scale.sort((a, b) => {
return Math.abs(a - value) - Math.abs(b - value);
})[0];
//console.log(value + ' is closest to ' + closestNum);
let num = (element) => element > closestNum;
let scaleIndex = co2Scale.findIndex(num);
let closestColor = colors[scaleIndex];
//console.log(scaleIndex, closestColor);
chrome.runtime.sendMessage({ action: 'updateIcon', value: { color: closestColor } });
};
const displayCarbonUsage = async (apiKey, region) => {
try {
await axios
.get('https://api.co2signal.com/v1/latest', {
params: {
countryCode: region,
},
headers: {
//please get your own token from CO2Signal https://www.co2signal.com/
'auth-token': apiKey,
},
})
.then((response) => {
let CO2 = Math.floor(response.data.data.carbonIntensity);
calculateColor(CO2);
loading.style.display = 'none';
form.style.display = 'none';
myregion.textContent = region;
usage.textContent =
Math.round(response.data.data.carbonIntensity) + ' grams (grams C02 emitted per kilowatt hour)';
fossilfuel.textContent =
response.data.data.fossilFuelPercentage.toFixed(2) +
'% (percentage of fossil fuels used to generate electricity)';
results.style.display = 'block';
});
} catch (error) {
console.log(error);
loading.style.display = 'none';
results.style.display = 'none';
errors.textContent = 'Sorry, we have no data for the region you have requested.';
}
};
// set up api key and region
const setUpUser = async (apiKey, regionName) => {
localStorage.setItem('apiKey', apiKey);
localStorage.setItem('regionName', regionName);
loading.style.display = 'block';
errors.textContent = '';
clearBtn.style.display = 'block';
//make initial call
displayCarbonUsage(apiKey, regionName);
};
// handle form submission
const handleSubmit = async (e) => {
e.preventDefault();
setUpUser(apiKey.value, region.value);
};
//initial checks
const init = async () => {
//if anything is in localStorage, pick it up
const storedApiKey = localStorage.getItem('apiKey');
const storedRegion = localStorage.getItem('regionName');
//set icon to be generic green
chrome.runtime.sendMessage({
action: 'updateIcon',
value: {
color: 'green',
},
});
if (storedApiKey === null || storedRegion === null) {
//if we don't have the keys, show the form
form.style.display = 'block';
results.style.display = 'none';
loading.style.display = 'none';
clearBtn.style.display = 'none';
errors.textContent = '';
} else {
//if we have saved keys/regions in localStorage, show results when they load
results.style.display = 'none';
form.style.display = 'none';
displayCarbonUsage(storedApiKey, storedRegion);
clearBtn.style.display = 'block';
}
};
const reset = async (e) => {
e.preventDefault();
//clear local storage for region only
localStorage.removeItem('regionName');
init();
};
form.addEventListener('submit', (e) => handleSubmit(e));
clearBtn.addEventListener('click', (e) => reset(e));
//start app
init();

@ -0,0 +1,28 @@
# Extensión del navegador Carbon Trigger: Código completo
Usando la API de señal C02 de tmrow para rastrear el uso de electricidad, cree una extensión de navegador para que pueda tener un recordatorio directamente en su navegador sobre el consumo de electricidad de su región. El uso de esta extensión ad hoc le ayudará a tomar decisiones sobre sus actividades basándose en esta información.
![extension screenshot](../start/extension-screenshot.png)
## Empezando
Deberá tener [npm](https://npmjs.com) instalado. Descargue una copia de este código en una carpeta de su computadora.
Instale todos los paquetes necesarios:
```
npm install
```
Construye la extensión desde webpack:
```
npm run build
```
Para instalar en Edge, use el menú de 'tres puntos' en la esquina superior derecha del navegador para encontrar el panel Extensiones. Desde allí, seleccione 'Cargar desempaquetado' para cargar una nueva extensión. Abra la carpeta 'dist' cuando se le solicite y se cargará la extensión. Para usarlo, necesitará una clave API para la API de CO2 Signal ([obtenga una aquí por correo electrónico](https://www.co2signal.com/) - ingrese su correo electrónico en el cuadro de esta página) y el [código para su región](http://api.electricitymap.org/v3/zones) correspondiente al [Mapa de electricidad](https://www.electricitymap.org/map) (en Boston, por ejemplo, utilizo 'US- NEISO').
![installing](../start/install-on-edge.png)
Una vez que se ingresa la clave API y la región en la interfaz de extensión, el punto de color en la barra de extensión del navegador debe cambiar para reflejar el uso de energía de su región y darle un indicador sobre las actividades de alto consumo de energía que serían apropiadas para usted. El concepto detrás de este sistema de "puntos" me lo dio la [extensión Energy Lollipop](https://energylollipop.com/) para las emisiones de California.

@ -0,0 +1,28 @@
# Carbon Trigger Browser Extension: Starter Code
Using tmrow's C02 Signal API to track electricity usage, build a browser extension so that you can have a reminder right in your browser about how heavy your region's electricity usage is. Using this extension ad hoc will help you to make judgement calls on your activities based on this information.
![extension screenshot](../extension-screenshot.png)
## Getting Started
You will need to have [npm](https://npmjs.com) installed. Download a copy of this code to a folder on your computer.
Install all the required packages:
```
npm install
```
Build the extension from webpack
```
npm run build
```
To install on Edge, use the 'three dot' menu on the top right corner of the browser to find the Extensions panel. From there, select 'Load Unpacked' to load a new extension. Open the 'dist' folder at the prompt and the extension will load. To use it, you will need an API key for CO2 Signal's API ([get one here via email](https://www.co2signal.com/) - enter your email in the box on this page) and the [code for your region](http://api.electricitymap.org/v3/zones) corresponding to the [Electricity Map](https://www.electricitymap.org/map) (in Boston, for example, I use 'US-NEISO').
![installing](../install-on-edge.png)
Once the API key and region is input into the extension interface, the colored dot in the browser extension bar should change to reflect your region's energy usage and give you a pointer on what energy-heavy activities would be appropriate for you to perform. The concept behind this 'dot' system was given to me by the [Energy Lollipop extension](https://energylollipop.com/) for California emissions.

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save