mirror of
https://github.com/microsoft/Web-Dev-For-Beginners.git
synced 2025-09-02 19:22:42 +02:00
Update Translations
This commit is contained in:
@@ -1,288 +0,0 @@
|
||||
# Plantillas HTML y rutas en una aplicación web
|
||||
|
||||
## [Pre-lecture prueba](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/41)
|
||||
|
||||
### 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/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/docs/Web/API/Document/getElementById).
|
||||
2. Clone el elemento de plantilla, usando [`cloneNode`](https://developer.mozilla.org/docs/Web/API/Node/cloneNode).
|
||||
3. Adjúntelo al DOM bajo un elemento visible, por ejemplo usando [ʻappendChild`](https://developer.mozilla.org/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/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/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/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, 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/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:
|
||||
|
||||

|
||||
|
||||
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/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/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/docs/Web/JavaScript/Reference/Functions/Arrow_functions) aquí para declarar nuestro controlador de eventos `popstate` por concisión, pero la función regular funcionaría igual.
|
||||
|
||||
|
||||
[](https://youtube.com/watch?v=JiXY9wlqUxQ "Arrow Functions")
|
||||
|
||||
|
||||
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](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/42)
|
||||
|
||||
## Revisión y autoestudio
|
||||
|
||||
**Tarea**: [Mejorar el enrutamiento](assignment.es.md)
|
@@ -1,309 +0,0 @@
|
||||
# Créer une application bancaire Partie 1 : Modèles HTML et routages dans une application Web
|
||||
|
||||
## Quiz préalable
|
||||
|
||||
[Quiz préalable](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/41?loc=fr)
|
||||
|
||||
### Introduction
|
||||
|
||||
Depuis l'avènement de JavaScript dans les navigateurs, les sites Web deviennent plus interactifs et complexes que jamais. Les technologies Web sont maintenant couramment utilisées pour créer des applications entièrement fonctionnelles qui s'exécutent directement dans un navigateur que nous appelons [applications Web](https://en.wikipedia.org/wiki/Web_application). Les applications Web étant hautement interactives, les utilisateurs ne souhaitent pas attendre un rechargement complet de la page à chaque fois qu'une action est effectuée. C'est pourquoi JavaScript est utilisé pour mettre à jour le HTML directement à l'aide du DOM, afin de fournir une expérience utilisateur plus fluide.
|
||||
|
||||
Dans cette leçon, nous allons poser les bases de la création d'une application Web bancaire, en utilisant des modèles HTML pour créer plusieurs écrans pouvant être affichés et mis à jour sans avoir à recharger toute la page HTML.
|
||||
|
||||
### Prérequis
|
||||
|
||||
Vous avez besoin d'un serveur Web local pour tester l'application Web que nous allons créer dans cette leçon. Si vous n'en avez pas, vous pouvez installer [Node.js](https://nodejs.org/fr) et utiliser la commande `npx lite-server` depuis le dossier de votre projet. Il créera un serveur Web local et ouvrira votre application dans un navigateur.
|
||||
|
||||
### Préparation
|
||||
|
||||
Sur votre ordinateur, créez un dossier nommé `bank` avec un fichier nommé `index.html` à l'intérieur. Nous allons commencer par ce code 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>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Modèles HTML
|
||||
|
||||
Si vous souhaitez créer plusieurs écrans pour une page Web, une solution serait de créer un fichier HTML pour chaque écran que vous souhaitez afficher. Cependant, cette solution présente quelques inconvénients :
|
||||
|
||||
- Vous devez recharger l'intégralité du code HTML lors du changement d'écran, ce qui peut être lent.
|
||||
- Il est difficile de partager des données entre les différents écrans.
|
||||
|
||||
Une autre approche consiste à n'avoir qu'un seul fichier HTML et à définir plusieurs [modèles HTML](https://developer.mozilla.org/docs/Web/HTML/Element/template) à l'aide de l'élément `<template>`. Un modèle est un bloc HTML réutilisable qui n'est pas affiché par le navigateur et doit être instancié lors de l'exécution à l'aide de JavaScript.
|
||||
|
||||
### Tâche
|
||||
|
||||
Nous allons créer une application bancaire avec deux écrans : la page de connexion et le tableau de bord. Tout d'abord, ajoutons dans le corps HTML un élément d'espace réservé que nous utiliserons pour instancier les différents écrans de notre application :
|
||||
|
||||
```html
|
||||
<div id="app">Loading...</div>
|
||||
```
|
||||
|
||||
Nous lui donnons un `id` pour le rendre plus facile à localiser avec JavaScript plus tard.
|
||||
|
||||
> Astuce : puisque le contenu de cet élément sera remplacé, nous pouvons mettre un message de chargement ou un indicateur qui s'affichera pendant le chargement de l'application.
|
||||
|
||||
Ensuite, ajoutons ci-dessous le modèle HTML pour la page de connexion. Pour l'instant nous n'y mettrons qu'un titre et une section contenant un lien que nous utiliserons pour effectuer la navigation.
|
||||
|
||||
```html
|
||||
<template id="login">
|
||||
<h1>Bank App</h1>
|
||||
<section>
|
||||
<a href="/dashboard">Login</a>
|
||||
</section>
|
||||
</template>
|
||||
```
|
||||
|
||||
Ensuite, nous ajouterons un autre modèle HTML pour la page du tableau de bord. Cette page contiendra différentes sections :
|
||||
|
||||
- Une en-tête avec un titre et un lien de déconnexion
|
||||
- Le solde courant du compte bancaire
|
||||
- Une liste des transactions, affichée dans un tableau
|
||||
|
||||
```html
|
||||
<template id="dashboard">
|
||||
<header>
|
||||
<h1>Bank App</h1>
|
||||
<a href="/login">Logout</a>
|
||||
</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>
|
||||
```
|
||||
|
||||
> Astuce : lors de la création de modèles HTML, si vous voulez voir à quoi cela ressemblera, vous pouvez commenter les lignes `<template>` et `</template>` en les entourant de `<!-- -->`.
|
||||
|
||||
✅ Pourquoi pensez-vous que nous utilisons les attributs `id` sur les modèles ? Pourrions-nous utiliser autre chose comme des classes ?
|
||||
|
||||
## Afficher des modèles avec JavaScript
|
||||
|
||||
Si vous essayez votre fichier HTML actuel dans un navigateur, vous verrez qu'il reste bloqué en affichant `Loading...`. C'est parce que nous devons ajouter du code JavaScript pour instancier et afficher les modèles HTML.
|
||||
|
||||
L'instanciation d'un modèle se fait généralement en 3 étapes :
|
||||
|
||||
1. Récupérez l'élément du modèle dans le DOM, par exemple en utilisant [`document.getElementById`](https://developer.mozilla.org/docs/Web/API/Document/getElementById).
|
||||
2. Clonez l'élément de modèle à l'aide de [`cloneNode`](https://developer.mozilla.org/docs/Web/API/Node/cloneNode).
|
||||
3. Attachez-le au DOM sous un élément visible, par exemple en utilisant [`appendChild`](https://developer.mozilla.org/docs/Web/API/Node/appendChild).
|
||||
|
||||
✅ Pourquoi avons-nous besoin de cloner le modèle avant de l'attacher au DOM ? Que pensez-vous qu'il se passerait si nous sautions cette étape?
|
||||
|
||||
### Tâche
|
||||
|
||||
Créez un nouveau fichier nommé `app.js` dans votre dossier de projet et importez ce fichier dans la section `<head>` de votre code HTML :
|
||||
|
||||
```html
|
||||
<script src="app.js" defer></script>
|
||||
```
|
||||
|
||||
Maintenant dans `app.js`, nous allons créer une nouvelle fonction `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);
|
||||
}
|
||||
```
|
||||
|
||||
Ce que nous faisons ici est exactement les 3 étapes décrites ci-dessus. Nous instancions le modèle avec l'ID `templateId` et plaçons son contenu cloné dans notre espace réservé d'application. Notez que nous devons utiliser `cloneNode(true)` pour copier l'intégralité du sous-arbre du modèle.
|
||||
|
||||
Appelez maintenant cette fonction avec l'un des modèles et regardez le résultat.
|
||||
|
||||
```js
|
||||
updateRoute('login');
|
||||
```
|
||||
|
||||
✅ Quel est le but de ce code `app.innerHTML = '';` ? Que se passe-t-il sans ?
|
||||
|
||||
## Création de routes
|
||||
|
||||
Lorsque nous parlons d'une application Web, nous appelons *Routage* l'intention de mapper des **URL** sur des écrans spécifiques qui doivent être affichés. Sur un site Web avec plusieurs fichiers HTML, cela se fait automatiquement car les chemins d'accès aux fichiers sont reflétés sur l'URL. Par exemple, avec ces fichiers dans votre dossier de projet :
|
||||
|
||||
```
|
||||
monsite/index.html
|
||||
monsite/login.html
|
||||
monsite/admin/index.html
|
||||
```
|
||||
|
||||
Si vous créez un serveur Web avec `mywebsite` comme racine, le mappage d'URL sera:
|
||||
|
||||
```
|
||||
https://site.com --> mywebsite/index.html
|
||||
https://site.com/login.html --> mywebsite/login.html
|
||||
https://site.com/admin/ --> mywebsite/admin/index.html
|
||||
```
|
||||
|
||||
Cependant, pour notre application Web, nous utilisons un seul fichier HTML contenant tous les écrans, ce comportement par défaut ne nous aidera donc pas. Nous devons créer cette carte manuellement et effectuer la mise à jour du modèle affiché à l'aide de JavaScript.
|
||||
|
||||
### Tâche
|
||||
|
||||
Nous utiliserons un objet simple pour implémenter une [carte](https://en.wikipedia.org/wiki/Associative_array) entre les chemins d'URL et nos modèles. Ajoutez cet objet en haut de votre fichier `app.js`.
|
||||
|
||||
```js
|
||||
const routes = {
|
||||
'/login': { templateId: 'login' },
|
||||
'/dashboard': { templateId: 'dashboard' },
|
||||
};
|
||||
```
|
||||
|
||||
Modifions maintenant un peu la fonction `updateRoute`. Au lieu de passer directement le `templateId` comme argument, nous voulons le récupérer en regardant d'abord l'URL actuelle, puis en utilisant notre carte pour obtenir la valeur d'ID de modèle correspondante. Nous pouvons utiliser [`window.location.pathname`](https://developer.mozilla.org/docs/Web/API/Location/pathname) pour obtenir uniquement la section du chemin de l'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);
|
||||
}
|
||||
```
|
||||
|
||||
Ici, nous avons mappé les routes que nous avons déclarées au modèle correspondant. Vous pouvez vérifier que celà fonctionne correctement en modifiant l'URL manuellement dans votre navigateur.
|
||||
|
||||
✅ Que se passe-t-il si vous saisissez un chemin inconnu dans l'URL ? Comment pourrions-nous résoudre cela?
|
||||
|
||||
## Ajout de navigation
|
||||
|
||||
La prochaine étape pour notre application consiste à ajouter la possibilité de naviguer entre les pages sans avoir à modifier l'URL manuellement. Cela implique deux choses :
|
||||
|
||||
1. Mise à jour de l'URL actuelle
|
||||
2. Mise à jour du modèle affiché en fonction de la nouvelle URL
|
||||
|
||||
Nous nous sommes déjà occupés de la deuxième partie avec la fonction `updateRoute`, nous devons donc trouver comment mettre à jour l'URL actuelle.
|
||||
|
||||
Il va falloir utiliser JavaScript et plus précisément le [`history.pushState`](https://developer.mozilla.org/docs/Web/API/History/pushState) qui permet de mettre à jour l'URL et créer une nouvelle entrée dans l'historique de navigation, sans recharger le code HTML.
|
||||
|
||||
> Remarque : Bien que l'élément d'ancrage HTML [`<a href>`](https://developer.mozilla.org/docs/Web/HTML/Element/a) puisse être utilisé seul pour créer des liens hypertexte à des URL différentes, il fera le navigateur recharger le HTML par défaut. Il est nécessaire d'empêcher ce comportement lors de la gestion du routage avec un javascript personnalisé, en utilisant la fonction preventDefault() sur l'événement click.
|
||||
|
||||
### Tâche
|
||||
|
||||
Créons une nouvelle fonction que nous pouvons utiliser pour naviguer dans notre application:
|
||||
|
||||
```js
|
||||
function navigate(path) {
|
||||
window.history.pushState({}, path, path);
|
||||
updateRoute();
|
||||
}
|
||||
```
|
||||
|
||||
Cette méthode met d'abord à jour l'URL actuelle en fonction du chemin donné, puis met à jour le modèle. La propriété `window.location.origin` renvoie la racine de l'URL, nous permettant de reconstruire une URL complète à partir d'un chemin donné.
|
||||
|
||||
Maintenant que nous avons cette fonction, nous pouvons résoudre le problème que nous avons si un chemin ne correspond à aucun itinéraire défini. Nous modifierons la fonction `updateRoute` en ajoutant une solution de secours à l'une des routes existantes si nous ne trouvons pas de correspondance.
|
||||
|
||||
```js
|
||||
function updateRoute() {
|
||||
const path = window.location.pathname;
|
||||
const route = routes[path];
|
||||
|
||||
if (!route) {
|
||||
return navigate('/login');
|
||||
}
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
Si un itinéraire ne peut pas être trouvé, nous allons maintenant rediriger vers la page de `login`.
|
||||
|
||||
Créons maintenant une fonction pour obtenir l'URL lorsqu'un lien est cliqué et pour empêcher le comportement de lien par défaut du navigateur :
|
||||
|
||||
```js
|
||||
function onLinkClick(event) {
|
||||
event.preventDefault();
|
||||
navigate(event.target.href);
|
||||
}
|
||||
```
|
||||
|
||||
Complétons le système de navigation en ajoutant des liens à nos liens *Login* et *Logout* dans le HTML.
|
||||
|
||||
```html
|
||||
<a href="/dashboard" onclick="onLinkClick(event)">Login</a>
|
||||
...
|
||||
<a href="/login" onclick="onLinkClick(event)">Logout</a>
|
||||
```
|
||||
|
||||
L'objet `event` ci-dessus capture l'événement `click` et le transmet à notre fonction `onLinkClick`.
|
||||
|
||||
Utiliser l'attribut [`onclick`](https://developer.mozilla.org/docs/Web/API/GlobalEventHandlers/onclick) lie l'événement `click` au code JavaScript, dans ce cas via l'appel à la fonction `navigate ()`.
|
||||
|
||||
Essayez de cliquer sur ces liens, vous devriez maintenant pouvoir naviguer entre les différents écrans de votre application.
|
||||
|
||||
✅ La méthode `history.pushState` fait partie de la norme HTML5 et est implémentée dans [tous les navigateurs modernes](https://caniuse.com/?search=pushState). Si vous créez une application Web pour les navigateurs plus anciens, il existe une astuce que vous pouvez utiliser à la place de cette API : en utilisant un [hash (`#`)](https://en.wikipedia.org/wiki/URI_fragment) avant le chemin que vous pouvez implémenter un routage qui fonctionne avec la navigation d'ancrage standard et ne recharge pas la page, car son but était de créer des liens internes au sein d'une page.
|
||||
|
||||
## Gestion des boutons Précédent et Suivant du navigateur
|
||||
|
||||
L'utilisation de `history.pushState` crée de nouvelles entrées dans l'historique de navigation du navigateur. Vous pouvez vérifier qu'en maintenant enfoncé le *bouton de retour* de votre navigateur, il devrait afficher quelque chose comme ceci:
|
||||
|
||||

|
||||
|
||||
Si vous essayez de cliquer plusieurs fois sur le bouton Précédent, vous verrez que l'URL actuelle change et que l'historique est mis à jour, mais le même modèle reste affiché.
|
||||
|
||||
C'est parce que l'application ne sait pas que nous devons appeler `updateRoute()` chaque fois que l'historique change. Si vous jetez un oeil à [ `la documentation de history.pushState`](https://developer.mozilla.org/docs/Web/API/History/pushState), vous pouvez voir que si l'état change - c'est que nous sommes passés à une URL différente - l'événement [ `popstate`](https://developer.mozilla.org/docs/Web/API/Window/popstate_event) est déclenché. Nous allons l'utiliser pour résoudre ce problème.
|
||||
|
||||
### Tâche
|
||||
|
||||
Pour nous assurer que le modèle affiché est mis à jour lorsque l'historique du navigateur change, nous allons attacher une nouvelle fonction qui appelle `updateRoute()`. Nous le ferons au bas de notre fichier `app.js`:
|
||||
|
||||
```js
|
||||
window.onpopstate = () => updateRoute();
|
||||
updateRoute();
|
||||
```
|
||||
|
||||
> Remarque : nous avons utilisé une [fonction fléchée](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Functions/Arrow_functions) ici pour déclarer notre gestionnaire d'événements `popstate` pour plus de concision, mais un la fonction normale fonctionnerait de la même manière.
|
||||
|
||||
Voici une vidéo de rappel sur les fonctions fléchées :
|
||||
|
||||
[](https://youtube.com/watch?v=OP6eEbOj2sc "Fonctions fléchées")
|
||||
|
||||
> 🎥 Cliquez sur l'image ci-dessus pour une vidéo sur les fonctions fléchées.
|
||||
|
||||
Essayez maintenant d'utiliser les boutons Précédent et Suivant de vos navigateurs, et vérifiez que la route affichée est bien mis à jour cette fois.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Défi
|
||||
|
||||
|
||||
Ajoutez un nouveau modèle et une route pour une troisième page qui affiche les crédits de cette application.
|
||||
|
||||
## Quiz de validation des connaissances
|
||||
|
||||
[Quiz de validation des connaissances](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/42?loc=fr)
|
||||
|
||||
## Révision et étude personnelle
|
||||
|
||||
Le routage est l'une des parties étonnamment délicates du développement Web, d'autant plus que le Web passe des comportements d'actualisation des pages aux actualisations des pages d'application à page unique. En savoir plus sur [comment le service Azure Static Web App](https://docs.microsoft.com/azure/static-web-apps/routes/?WT.mc_id=academic-77807-sagibbon) gère le routage . Pouvez-vous expliquer pourquoi certaines des décisions décrites dans ce document sont nécessaires?
|
||||
|
||||
## Affectation
|
||||
|
||||
[Améliorer le routage](assignment.fr.md)
|
@@ -1,304 +0,0 @@
|
||||
# एक वेब ऐप में बैंकिंग ऐप पार्ट 1: HTML टेम्प्लेट और रूट बनाएं
|
||||
|
||||
## पूर्व व्याख्यान प्रश्नोत्तरी
|
||||
|
||||
[पूर्व व्याख्यान प्रश्नोत्तरी](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/41?loc=hi)
|
||||
|
||||
### परिचय
|
||||
|
||||
ब्राउज़रों में जावास्क्रिप्ट के आगमन के बाद से, वेबसाइटें पहले से कहीं अधिक इंटरैक्टिव और जटिल होती जा रही हैं। वेब प्रौद्योगिकियों का अब आमतौर पर पूरी तरह कार्यात्मक अनुप्रयोग बनाने के लिए उपयोग किया जाता है जो सीधे एक ब्राउज़र में चलता है जिसे हम [वेब एप्लिकेशन](https://en.wikipedia.org/wiki/Web_application) कहते हैं। चूंकि वेब ऐप्स अत्यधिक संवादात्मक हैं, इसलिए उपयोगकर्ता हर बार किसी क्रिया के पूरा होने तक पूर्ण पृष्ठ पुनः लोड होने की प्रतीक्षा नहीं करना चाहते हैं। यही कारण है कि एक चिकनी उपयोगकर्ता अनुभव प्रदान करने के लिए जावास्क्रिप्ट का उपयोग सीधे डोम का उपयोग करके HTML को अपडेट करने के लिए किया जाता है।
|
||||
|
||||
इस पाठ में, हम बैंक वेब ऐप बनाने के लिए नींव रखने जा रहे हैं, HTML टेम्प्लेट का उपयोग करके ऐसी कई स्क्रीन बना सकते हैं जिन्हें प्रदर्शित किया जा सकता है और पूरे HTML पृष्ठ को फिर से लोड किए बिना अपडेट किया जा सकता है।
|
||||
|
||||
### शर्त
|
||||
|
||||
इस पाठ में हमारे द्वारा बनाए गए वेब ऐप का परीक्षण करने के लिए आपको एक स्थानीय वेब सर्वर की आवश्यकता होगी। यदि आपके पास एक नहीं है, तो आप [Node.js](https://nodejs.org) को स्थापित कर सकते हैं और अपने प्रोजेक्ट फ़ोल्डर से कमांड `npx lite-server` का उपयोग कर सकते हैं। यह एक स्थानीय वेब सर्वर बनाएगा और आपके ऐप को एक ब्राउज़र में खोलेगा।
|
||||
|
||||
### तैयारी
|
||||
|
||||
अपने कंप्यूटर पर, उसके अंदर `index.html` नामक फ़ाइल के साथ `bank` नामक एक फ़ोल्डर बनाएँ। हम इस 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 टेम्पलेट
|
||||
|
||||
यदि आप एक वेब पेज के लिए कई स्क्रीन बनाना चाहते हैं, तो एक समाधान यह होगा कि आप जिस भी स्क्रीन को दिखाना चाहते हैं, उसके लिए एक HTML फाइल बनाई जाए। हालाँकि, यह समाधान कुछ असुविधा के साथ आता है:
|
||||
|
||||
- आपको स्क्रीन स्विच करते समय पूरे HTML को फिर से लोड करना होगा, जो धीमा हो सकता है।
|
||||
- विभिन्न स्क्रीन के बीच डेटा साझा करना मुश्किल है।
|
||||
|
||||
एक अन्य दृष्टिकोण में केवल एक HTML फ़ाइल है, और `<template>` तत्व का उपयोग करके कई [HTML टेम्पलेट्स](https://developer.mozilla.org/docs/Web/HTML/Element/template) को परिभाषित करें। एक टेम्पलेट एक पुन: प्रयोज्य HTML ब्लॉक है जो ब्राउज़र द्वारा प्रदर्शित नहीं किया जाता है, और जावास्क्रिप्ट का उपयोग करके रनटाइम पर तत्काल किया जाना चाहिए।
|
||||
|
||||
### टास्क
|
||||
|
||||
हम दो स्क्रीन के साथ एक बैंक ऐप बनाएंगे: लॉगिन पेज और डैशबोर्ड। सबसे पहले, एचटीएमएल बॉडी में एक प्लेसहोल्डर तत्व जोड़ें, जिसका उपयोग हम अपने ऐप की विभिन्न स्क्रीन को इंस्टेंट करने के लिए करेंगे:
|
||||
|
||||
```html
|
||||
<div id="app">Loading...</div>
|
||||
```
|
||||
|
||||
हम इसे एक `id` दे रहे हैं ताकि बाद में इसे जावास्क्रिप्ट के साथ ढूंढना आसान हो सके।
|
||||
|
||||
> युक्ति: चूंकि इस तत्व की सामग्री को बदल दिया जाएगा, हम एक लोडिंग संदेश या संकेतक में डाल सकते हैं जो ऐप लोड होने के दौरान दिखाया जाएगा।
|
||||
|
||||
अगला, लॉगिन पृष्ठ के लिए HTML टेम्पलेट के नीचे जोड़ें। अभी के लिए हम केवल एक शीर्षक और एक अनुभाग रखेंगे जिसमें एक लिंक होगा जिसका उपयोग हम नेविगेशन करने के लिए करेंगे।
|
||||
|
||||
```html
|
||||
<template id="login">
|
||||
<h1>Bank App</h1>
|
||||
<section>
|
||||
<a href="/dashboard">Login</a>
|
||||
</section>
|
||||
</template>
|
||||
```
|
||||
|
||||
फिर हम डैशबोर्ड पृष्ठ के लिए एक और HTML टेम्पलेट जोड़ेंगे। इस पृष्ठ में विभिन्न अनुभाग होंगे:
|
||||
|
||||
- शीर्षक के साथ हैडर और लॉगआउट लिंक
|
||||
- बैंक खाते का वर्तमान संतुलन
|
||||
- लेनदेन की एक सूची, एक तालिका में प्रदर्शित
|
||||
|
||||
```html
|
||||
<template id="dashboard">
|
||||
<header>
|
||||
<h1>Bank App</h1>
|
||||
<a href="/login">Logout</a>
|
||||
</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>
|
||||
```
|
||||
|
||||
> युक्ति: HTML टेम्प्लेट बनाते समय, यदि आप यह देखना चाहते हैं कि यह कैसा दिखेगा, तो आप `<template>` और `</template>` लाइनों को `<!-- -->` के साथ संलग्न करके टिप्पणी कर सकते हैं।
|
||||
|
||||
✅ आपको क्या लगता है कि हम टेम्प्लेट पर `id` विशेषताओं का उपयोग क्यों करते हैं? क्या हम कक्षाओं की तरह कुछ और उपयोग कर सकते हैं?
|
||||
|
||||
## जावास्क्रिप्ट के साथ टेम्पलेट्स प्रदर्शित करना
|
||||
|
||||
यदि आप अपनी वर्तमान HTML फ़ाइल को किसी ब्राउज़र में आज़माते हैं, तो आप देखेंगे कि यह `Loading...` को प्रदर्शित करता है। ऐसा इसलिए है क्योंकि हमें HTML टेम्प्लेट को इंस्टेंट करने और प्रदर्शित करने के लिए कुछ जावास्क्रिप्ट कोड जोड़ना होगा।
|
||||
|
||||
एक टेम्पलेट को तत्काल बनाना आमतौर पर 3 चरणों में किया जाता है:
|
||||
1. DOM में टेम्पलेट तत्व को पुनः प्राप्त करें, उदाहरण के लिए [`document.getElementById`](https://developer.mozilla.org/docs/Web/API/Document/getEgetById) का उपयोग करके।
|
||||
2. टेम्पलेट तत्व को क्लोन करें, [`cloneNode`](https://developer.mozilla.org/docs/Web/API/Node/cloneNode) का उपयोग करके।
|
||||
3. इसे एक दृश्य तत्व के तहत DOM में संलग्न करें, उदाहरण के लिए [`appendChild`](https://developer.mozilla.org/docs/Web/API/Node/appendChild) का उपयोग करके।
|
||||
|
||||
✅ DOM को संलग्न करने से पहले हमें टेम्प्लेट को क्लोन करने की आवश्यकता क्यों है? आपको क्या लगता है कि अगर हम इस कदम को छोड़ देते तो क्या होता?
|
||||
|
||||
### टास्क
|
||||
|
||||
अपने प्रोजेक्ट फ़ोल्डर में `app.js` नामक एक नई फ़ाइल बनाएँ और उस फ़ाइल को अपने HTML के `<head>`अनुभाग में आयात करें:
|
||||
|
||||
```html
|
||||
<script src="app.js" defer></script>
|
||||
```
|
||||
|
||||
अब `app.js` में, हम एक नया फ़ंक्शन `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);
|
||||
}
|
||||
```
|
||||
|
||||
हम यहाँ क्या करते हैं, ऊपर वर्णित 3 चरण हैं। हम आईडी `templateId` के साथ टेम्पलेट को इंस्टेंट करते हैं, और हमारे ऐप प्लेसहोल्डर के भीतर इसकी क्लोन सामग्री डालते हैं। ध्यान दें कि हमें टेम्पलेट के पूरे उपप्रकार को कॉपी करने के लिए `cloneNode(true)` का उपयोग करने की आवश्यकता है।
|
||||
|
||||
अब इस फंक्शन को टेम्प्लेट में से किसी एक पर कॉल करें और परिणाम देखें।
|
||||
|
||||
```js
|
||||
updateRoute('login');
|
||||
```
|
||||
|
||||
✅ इस कोड का उद्देश्य क्या है `app.innerHTML =' '; `? इसके बिना क्या होता है?
|
||||
|
||||
## रूटस बनाना
|
||||
|
||||
जब एक वेब ऐप के बारे में बात की जाती है, तो हम *रूटिंग* को मैप करने का इरादा **URL** विशिष्ट स्क्रीन पर प्रदर्शित करते हैं जिन्हें प्रदर्शित किया जाना चाहिए। एकाधिक HTML फ़ाइलों वाली वेब साइट पर, यह स्वचालित रूप से किया जाता है क्योंकि URL पर फ़ाइल पथ प्रतिबिंबित होते हैं। उदाहरण के लिए, अपने प्रोजेक्ट फ़ोल्डर में इन फ़ाइलों के साथ:
|
||||
|
||||
```
|
||||
mywebsite/index.html
|
||||
mywebsite/login.html
|
||||
mywebsite/admin/index.html
|
||||
```
|
||||
|
||||
यदि आप रूट के रूप में `mywebsite` के साथ एक वेब सर्वर बनाते हैं, तो URL मैपिंग निम्न होगी:
|
||||
|
||||
```
|
||||
https://site.com --> mywebsite/index.html
|
||||
https://site.com/login.html --> mywebsite/login.html
|
||||
https://site.com/admin/ --> mywebsite/admin/index.html
|
||||
```
|
||||
|
||||
हालाँकि, हमारे वेब ऐप के लिए हम सभी स्क्रीन वाले एक एकल HTML फ़ाइल का उपयोग कर रहे हैं ताकि यह डिफ़ॉल्ट व्यवहार हमारी मदद न करे। हमें इस नक्शे को मैन्युअल रूप से बनाना होगा और जावास्क्रिप्ट का उपयोग करके प्रदर्शित टेम्प्लेट को अपडेट करना होगा।
|
||||
|
||||
### टास्क
|
||||
|
||||
URL पथ और हमारे टेम्प्लेट के बीच एक [मैप](https://en.wikipedia.org/wiki/Associative_array) को लागू करने के लिए हम एक साधारण वस्तु का उपयोग करेंगे। इस ऑब्जेक्ट को अपने `app.js` फ़ाइल के शीर्ष पर जोड़ें।
|
||||
|
||||
```js
|
||||
const routes = {
|
||||
'/login': { templateId: 'login' },
|
||||
'/dashboard': { templateId: 'dashboard' },
|
||||
};
|
||||
```
|
||||
|
||||
अब चलो `updateRoute` फ़ंक्शन को थोड़ा संशोधित करते हैं। एक तर्क के रूप में सीधे `templateId` पास करने के बजाय, हम पहले वर्तमान URL को देखकर इसे पुनः प्राप्त करना चाहते हैं, और फिर संबंधित टेम्पलेट आईडी मान प्राप्त करने के लिए हमारे मानचित्र का उपयोग करते हैं। URL से केवल पथ अनुभाग प्राप्त करने के लिए हम [`window.location.pathname`](https://developer.mozilla.org/docs/Web/API/Location/pathname) का उपयोग कर सकते हैं।
|
||||
|
||||
```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);
|
||||
}
|
||||
```
|
||||
|
||||
यहां हमने संबंधित टेम्प्लेट में घोषित मार्गों को मैप किया। आप इसे आज़मा सकते हैं कि यह आपके ब्राउज़र में URL को मैन्युअल रूप से बदलकर सही ढंग से काम करता है।
|
||||
|
||||
✅ यदि आप URL में अज्ञात पथ दर्ज करते हैं तो क्या होगा? हम इसे कैसे हल कर सकते हैं?
|
||||
|
||||
## नेवीगेशन जोड़ना
|
||||
|
||||
हमारे एप्लिकेशन के लिए अगला चरण मैन्युअल रूप से URL बदलने के बिना पृष्ठों के बीच नेविगेट करने की संभावना को जोड़ना है। इसका तात्पर्य दो चीजों से है:
|
||||
|
||||
1. वर्तमान URL को अद्यतन करना
|
||||
2. नए URL के आधार पर प्रदर्शित टेम्प्लेट को अपडेट करना
|
||||
|
||||
हमने पहले ही `updateRoute` फ़ंक्शन के साथ दूसरे भाग का ध्यान रखा, इसलिए हमें यह पता लगाना होगा कि वर्तमान URL को कैसे अपडेट किया जाए।
|
||||
|
||||
हमें जावास्क्रिप्ट और अधिक विशेष रूप से [`history.pushState`](https://developer.mozilla.org/docs/Web/API/History/pushState) का उपयोग करना होगा जो URL को अपडेट करने की अनुमति देता है HTML को पुनः लोड किए बिना, ब्राउज़िंग इतिहास में एक नई प्रविष्टि बनाएँ।
|
||||
|
||||
> नोट: जबकि HTML एंकर तत्व [`<a href>`] (https://developer.mozilla.org/docs/Web/HTML/Element/a) का उपयोग हाइपरलिंक बनाने के लिए स्वयं किया जा सकता है विभिन्न यूआरएल, यह ब्राउज़र को डिफ़ॉल्ट रूप से HTML को फिर से लोड करेगा। क्लिक करें घटना पर preventDefault() फ़ंक्शन का उपयोग करके कस्टम जावास्क्रिप्ट के साथ रूटिंग को हैंडल करते समय इस व्यवहार को रोकना आवश्यक है।
|
||||
|
||||
### टास्क
|
||||
|
||||
आइए एक नया फ़ंक्शन बनाएं जिसे हम अपने ऐप में नेविगेट करने के लिए उपयोग कर सकते हैं:
|
||||
|
||||
```js
|
||||
function navigate(path) {
|
||||
window.history.pushState({}, path, path);
|
||||
updateRoute();
|
||||
}
|
||||
```
|
||||
|
||||
यह विधि पहले दिए गए पथ के आधार पर वर्तमान URL को अपडेट करती है, फिर टेम्पलेट को अपडेट करती है। प्रॉपर्टी `window.location.origin` URL रूट लौटाती है, जिससे हम किसी दिए गए पथ से एक पूर्ण URL का पुनर्निर्माण कर सकते हैं।
|
||||
|
||||
अब जब हमारे पास यह फ़ंक्शन है, तो हम इस समस्या का ध्यान रख सकते हैं कि अगर कोई रास्ता किसी परिभाषित मार्ग से मेल नहीं खाता है। यदि हम एक मैच नहीं पा सकते हैं, तो हम मौजूदा मार्ग में से किसी एक में वापसी को जोड़कर `updateRoute` फ़ंक्शन को संशोधित करेंगे।
|
||||
|
||||
```js
|
||||
function updateRoute() {
|
||||
const path = window.location.pathname;
|
||||
const route = routes[path];
|
||||
|
||||
if (!route) {
|
||||
return navigate('/login');
|
||||
}
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
यदि कोई मार्ग नहीं मिल रहा है, तो हम अब `login` पेज पर रीडायरेक्ट करेंगे।
|
||||
Now let's create a function to get the URL when a link is clicked, and to prevent the browser's default link behavior:
|
||||
|
||||
```js
|
||||
function onLinkClick(event) {
|
||||
event.preventDefault();
|
||||
navigate(event.target.href);
|
||||
}
|
||||
```
|
||||
|
||||
HTML में हमारे *लॉगिन* और *लॉगआउट* लिंक से बाइंडिंग जोड़कर नेविगेशन सिस्टम को पूरा करें।
|
||||
|
||||
```html
|
||||
<a href="/dashboard" onclick="onLinkClick(event)">Login</a>
|
||||
...
|
||||
<a href="/login" onclick="onLinkClick(event)">Logout</a>
|
||||
```
|
||||
|
||||
[`onclick`](https://developer.mozilla.org/docs/Web/API/GlobalEventHandlers/onclick) विशेषता का उपयोग करके जावास्क्रिप्ट कोड पर `click` ईवेंट को बांधें, यहाँ `navigate()` फ़ंक्शन पर कॉल करें।
|
||||
|
||||
इन लिंक पर क्लिक करने का प्रयास करें, अब आपको अपने ऐप के विभिन्न स्क्रीन के बीच नेविगेट करने में सक्षम होना चाहिए।
|
||||
|
||||
✅ `History.pushState` विधि HTML5 मानक का हिस्सा है और [सभी आधुनिक ब्राउज़रों](https://caniuse.com/?search=pushState) में लागू किया गया है। यदि आप पुराने ब्राउज़रों के लिए एक वेब ऐप बना रहे हैं, तो इस एपीआई के स्थान पर आप एक ट्रिक का उपयोग कर सकते हैं: इससे पहले [हैश (`#`)](https://en.wikipedia.org/wiki/URI_fragment) का उपयोग कर पथ आप नियमित एंकर नेविगेशन के साथ काम करने वाले रूटिंग को लागू कर सकते हैं और पृष्ठ को फिर से लोड नहीं करते हैं, क्योंकि इसका उद्देश्य एक पृष्ठ के भीतर आंतरिक लिंक बनाना था।
|
||||
|
||||
## ब्राउजर के बैक और फॉरवर्ड बटन को हैंडल करना
|
||||
|
||||
`History.pushState` का उपयोग करके ब्राउज़र के नेविगेशन इतिहास में नई प्रविष्टियाँ बनाता है। आप देख सकते हैं कि आपके ब्राउज़र का *बैक बटन* पकड़कर, इसे कुछ इस तरह प्रदर्शित करना चाहिए:
|
||||
|
||||

|
||||
|
||||
यदि आप कुछ बार बैक बटन पर क्लिक करने का प्रयास करते हैं, तो आप देखेंगे कि वर्तमान URL बदल जाता है और इतिहास अपडेट हो जाता है, लेकिन वही टेम्पलेट प्रदर्शित होता रहता है।
|
||||
|
||||
ऐसा इसलिए है क्योंकि हमें नहीं पता है कि इतिहास बदलने के लिए हमें हर बार `updateRoute()` को कॉल करना होगा। यदि आप [`history.pushState` दस्तावेज़ीकरण](https://developer.mozilla.org/docs/Web/API/History/pushState) पर एक नज़र डालते हैं, तो आप देख सकते हैं कि यदि राज्य बदलता है - इसका अर्थ है कि हम एक अलग URL पर चले गए - [`popstate`] (https://developer.mozilla.org/docs/Web/API/Window/popstate_event) इवेंट ट्रिगर है। हम उस समस्या को ठीक करने के लिए इसका उपयोग करेंगे।
|
||||
|
||||
### टास्क
|
||||
|
||||
यह सुनिश्चित करने के लिए कि ब्राउज़र इतिहास में परिवर्तन होने पर प्रदर्शित टेम्प्लेट को अपडेट किया जाता है, हम एक नया फ़ंक्शन संलग्न करेंगे जो `updateRoute()` कहता है। हम अपने `app.js` फ़ाइल के नीचे करेंगे:
|
||||
|
||||
```js
|
||||
window.onpopstate = () => updateRoute();
|
||||
updateRoute();
|
||||
```
|
||||
|
||||
> नोट: हमने एक [एरो फंक्शन](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Reference/Arrow_functions) का इस्तेमाल किया है ताकि हम अपने `poopstate` ईवेंट हैंडलर को संक्षिप्तता के लिए घोषित कर सकें, लेकिन नियमित कार्य एक ही काम करेगा।
|
||||
|
||||
यहां एरो फंक्शन पर एक ताज़ा वीडियो है:
|
||||
|
||||
[](https://youtube.com/watch?v=OP6eEbOj2sc "एरो फंक्शन")
|
||||
|
||||
> तीर के कार्यों के बारे में वीडियो के लिए ऊपर दी गई छवि पर क्लिक करें।
|
||||
|
||||
अब अपने ब्राउज़र के बैक और फ़ॉरवर्ड बटन का उपयोग करने का प्रयास करें, और जांचें कि इस बार प्रदर्शित मार्ग सही ढंग से अपडेट किया गया है।
|
||||
|
||||
---
|
||||
|
||||
## 🚀 चुनौती
|
||||
|
||||
तीसरे पृष्ठ के लिए एक नया टेम्प्लेट और रूट जोड़ें जो इस ऐप के लिए क्रेडिट दिखाता है।
|
||||
|
||||
## व्याख्यान उपरांत प्रश्नोत्तरी
|
||||
|
||||
[व्याख्यान उपरांत प्रश्नोत्तरी](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/42?loc=hi)
|
||||
|
||||
## समीक्षा और स्व अध्ययन
|
||||
|
||||
रूटिंग वेब विकास के आश्चर्यजनक रूप से मुश्किल भागों में से एक है, विशेष रूप से वेब पेज रीफ्रेश बिहेवियर से लेकर सिंगल पेज एप्लीकेशन पेज रिफ्रेश तक चलता है। [कैसे Azure स्टेटिक वेब ऐप सेवा](https://docs.microsoft.com/azure/static-web-apps/routes/?WT.mc_id=academic-77807-sagibbon) के बारे में थोड़ा पढ़ें रूटिंग । क्या आप बता सकते हैं कि उस दस्तावेज़ पर वर्णित कुछ निर्णय क्यों आवश्यक हैं?
|
||||
|
||||
## असाइनमेंट
|
||||
|
||||
[रूटिंग में सुधार करें](assignment.hi.md)
|
@@ -1,306 +0,0 @@
|
||||
# Creazione di un'App Bancaria Parte 1: Modelli HTML e Rotte in un'app web
|
||||
|
||||
## Quiz Pre-Lezione
|
||||
|
||||
[Quiz Pre-Lezione](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/41?loc=it)
|
||||
|
||||
### Introduzione
|
||||
|
||||
Dall'avvento di JavaScript nei browser, i siti web stanno diventando più interattivi e complessi che mai. Le tecnologie web sono ora comunemente utilizzate per creare applicazioni completamente funzionali che vengono eseguite direttamente in un browser che vengono chiamate [applicazioni web](https://it.wikipedia.org/wiki/Applicazione_web). Poiché le app web sono altamente interattive, gli utenti non desiderano attendere il ricaricamento di una pagina intera ogni volta che viene eseguita un'azione. Ecco perché JavaScript viene utilizzato per aggiornare l'HTML direttamente utilizzando il DOM, per fornire un'esperienza utente più fluida.
|
||||
|
||||
In questa lezione, getteremo le basi per creare un'app web bancaria, utilizzando modelli HTML per creare più schermate che possono essere visualizzate e aggiornate senza dover ricaricare l'intera pagina HTML.
|
||||
|
||||
### Prerequisito
|
||||
|
||||
È necessario un server web locale per testare l'app web che verrà creata in questa lezione. Se non ne ha uno, si può installare [Node.js](https://nodejs.org) e utilizzare il comando `npx lite-server` dalla cartella del progetto. Creerà un server web locale e aprirà la propria app in un browser.
|
||||
|
||||
### Preparazione
|
||||
|
||||
Sul proprio computer, creare una cartella denominata `bank` con un file denominato `index.html` al suo interno. Si inizierà da questo codice [boilerplate](https://en.wikipedia.org/wiki/Boilerplate_code) HTML:
|
||||
|
||||
```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>
|
||||
<!-- Qui è dove si lavorerà -->
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Modelli HTML.
|
||||
|
||||
Se si desidera creare più schermate per una pagina Web, una soluzione potrebbe essere la creazione di un file HTML per ogni schermata che si desidera visualizzare. Tuttavia, questa soluzione presenta alcuni inconvenienti:
|
||||
|
||||
- È necessario ricaricare l'intero HTML quando si cambia schermata, il che può essere lento.
|
||||
- È difficile condividere i dati tra le diverse schermate.
|
||||
|
||||
Un altro approccio consiste nell'avere un solo file HTML e definire più [modelli HTML](https://developer.mozilla.org/docs/Web/HTML/Element/template) utilizzando l'elemento `<template>`. Un modello è un blocco HTML riutilizzabile che non viene visualizzato dal browser e deve essere istanziato in fase di esecuzione utilizzando JavaScript.
|
||||
|
||||
### Attività
|
||||
|
||||
Verrà creata un'app bancaria con due schermate: la pagina di accesso e il cruscotto. Innanzitutto, si aggiunge nel corpo dell'HTML un elemento segnaposto che si utilizzerà per istanziare le diverse schermate dell'app:
|
||||
|
||||
```html
|
||||
<div id="app">Loading...</div>
|
||||
```
|
||||
|
||||
Viene fornito un `ID` all'elemento per renderlo più facilmente individuabile con JavaScript in seguito.
|
||||
|
||||
> Suggerimento: poiché il contenuto di questo elemento verrà sostituito, si può inserire un messaggio di caricamento o un indicatore che verrà mostrato durante il caricamento dell'app.
|
||||
|
||||
Successivamente, si aggiunge il modello HTML seguente per la pagina di accesso. Per ora si inserirà solo un titolo e una sezione contenente un collegamento che si utilizzerà per eseguire la navigazione.
|
||||
|
||||
```html
|
||||
<template id="login">
|
||||
<h1>Bank App</h1>
|
||||
<section>
|
||||
<a href="/dashboard">Login</a>
|
||||
</section>
|
||||
</template>
|
||||
```
|
||||
|
||||
Quindi si aggiungerà un altro modello HTML per la pagina del cruscotto. Questa pagina conterrà diverse sezioni:
|
||||
|
||||
- Un'intestazione con un titolo e un collegamento di disconnessione
|
||||
- Saldo corrente del conto bancario
|
||||
- Un elenco di transazioni, visualizzato in una tabella
|
||||
|
||||
```html
|
||||
<template id="dashboard">
|
||||
<header>
|
||||
<h1>Bank App</h1>
|
||||
<a href="/login">Logout</a>
|
||||
</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>
|
||||
```
|
||||
|
||||
> Suggerimento: durante la creazione di modelli HTML, se si vuole vedere come apparirà, si possono commentare le righe tra `<template>` `</template>` racchiudendole `tra <!- ->-->`.
|
||||
|
||||
✅ Perché si pensa che vengano utilizzati gli attributi `id` sui modelli? Si potrebbe usare qualcos'altro come le classi?
|
||||
|
||||
## Visualizzazione di modelli con JavaScript
|
||||
|
||||
Se si prova il proprio file HTML corrente in un browser, si vedrà che si blocca visualizzando `Loading ...` Questo perché si deve aggiungere del codice JavaScript per istanziare e visualizzare i modelli HTML.
|
||||
|
||||
L'istanza di un modello viene solitamente eseguita in 3 passaggi:
|
||||
|
||||
1. Recuperare l'elemento del modello nel DOM, ad esempio utilizzando [`document.getElementById`](https://developer.mozilla.org/it/docs/Web/API/Document/getElementById).
|
||||
2. Clonare l'elemento template, usando [`cloneNode`](https://developer.mozilla.org/docs/Web/API/Node/cloneNode).
|
||||
3. Collegarlo al DOM sotto un elemento visibile, ad esempio utilizzando [`appendChild`](https://developer.mozilla.org/it/docs/Web/API/Node/appendChild).
|
||||
|
||||
✅ Perché è necessario clonare il modello prima di collegarlo al DOM? Cosa si pensa che accadrebbe se venisse saltato questo passaggio?
|
||||
|
||||
### Attività
|
||||
|
||||
Creare un nuovo file denominato `app.js` nella cartella del progetto e importare quel file nella sezione `<head>` del proprio HTML:
|
||||
|
||||
```html
|
||||
<script src="app.js" defer></script>
|
||||
```
|
||||
|
||||
Ora in `app.js`, verrà creata una nuova funzione `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);
|
||||
}
|
||||
```
|
||||
|
||||
Quello che si fa qui sono esattamente i 3 passaggi descritti sopra. Si istanza il modello con l'id `templateId` e si inserisce il suo contenuto clonato nel segnaposto dell'app. Notare che si deve usare `cloneNode (true)` per copiare l'intero sottoalbero del modello.
|
||||
|
||||
Ora chiamare questa funzione con uno dei template e guardare il risultato.
|
||||
|
||||
```js
|
||||
updateRoute('login');
|
||||
```
|
||||
|
||||
✅ Qual è lo scopo di questo codice `app.innerHTML = '';`? Cosa succede senza di essa?
|
||||
|
||||
## Creazione di rotte
|
||||
|
||||
Quando si parla di un'app web, si definisce *Routing* (instradamento) l'intento di mappare gli **URL** a schermate specifiche che dovrebbero essere visualizzate. Su un sito web con più file HTML, questa operazione viene eseguita automaticamente poiché i percorsi dei file si riflettono sull'URL. Ad esempio, con questi file nella cartella del progetto:
|
||||
|
||||
```
|
||||
mywebsite/index.html
|
||||
mywebsite/login.html
|
||||
mywebsite/admin/index.html
|
||||
```
|
||||
|
||||
Se si crea un server web con `mywebsite` come radice, la mappatura dell'URL sarà:
|
||||
|
||||
```
|
||||
https://site.com --> mywebsite/index.html
|
||||
https://site.com/login.html --> mywebsite/login.html
|
||||
https://site.com/admin/ --> mywebsite/admin/index.html
|
||||
```
|
||||
|
||||
Tuttavia, per l'app web in costruzione si utilizza un singolo file HTML contenente tutte le schermate, quindi questo comportamento predefinito non sarà di aiuto. Si deve creare questa mappa manualmente ed eseguire l'aggiornamento del modello visualizzato utilizzando JavaScript.
|
||||
|
||||
### Attività
|
||||
|
||||
Si userà un semplice oggetto per implementare una [mappa](https://it.wikipedia.org/wiki/Array_associativo) tra i percorsi degli URL e i propri modelli. Aggiungere questo oggetto all'inizio del file `app.js`.
|
||||
|
||||
```js
|
||||
const routes = {
|
||||
'/login': { templateId: 'login' },
|
||||
'/dashboard': { templateId: 'dashboard' },
|
||||
};
|
||||
```
|
||||
|
||||
Ora modificare un po' la funzione `updateRoute`. Invece di passare direttamente il `templateId` come argomento, lo si vuole recuperare guardando prima l'URL corrente, quindi utilizzndo la mappa per ottenere il valore dell'ID del modello corrispondente. Si può usare [`window.location.pathname`](https://developer.mozilla.org/docs/Web/API/Location/pathname) per ottenere solo la sezione del percorso dall'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);
|
||||
}
|
||||
```
|
||||
|
||||
Qui sono state mappato le rotte che sono state dichiarate al modello corrispondente. Si può provare se funziona correttamente modificando manualmente l'URL nel proprio browser.
|
||||
|
||||
✅ Cosa succede se si inserisce un percorso sconosciuto nell'URL? Come potrebbe essere risolto questo problema?
|
||||
|
||||
## Aggiungere navigazione
|
||||
|
||||
Il prossimo passo per la costruzione dell'app è aggiungere la possibilità di navigare tra le pagine senza dover modificare manualmente l'URL. Questo implica due cose:
|
||||
|
||||
1. Aggiornamento dell'URL corrente
|
||||
2. Aggiornamento del modello visualizzato in base al nuovo URL
|
||||
|
||||
E' già stata trattata la seconda parte con la funzione `updateRoute`, quindi occorre capire come aggiornare l'URL corrente.
|
||||
|
||||
Si dovrà utilizzare JavaScript e più precisamente [history.pushState](https://developer.mozilla.org/docs/Web/API/History/pushState) che permette di aggiornare l'URL e creare una nuova voce nella cronologia di navigazione, senza ricaricare l'HTML.
|
||||
|
||||
> Nota: Sebbene l'elemento HTML ancora [`<a href>`](https://developer.mozilla.org/it/docs/Web/HTML/Element/a) possa essere usato da solo per creare collegamenti ipertestuali a diversi URL, è anche in grado di fare ricaricare al browser l'HTML nella modalità predefinita. È necessario prevenire questo comportamento quando si gestisce il routing con javascript personalizzato, utilizzando la funzione preventDefault() sull'evento click.
|
||||
|
||||
### Attività
|
||||
|
||||
Si crea una nuova funzione da utilizzare per navigare nell'app:
|
||||
|
||||
```js
|
||||
function navigate(path) {
|
||||
window.history.pushState({}, path, path);
|
||||
updateRoute();
|
||||
}
|
||||
```
|
||||
|
||||
Questo metodo aggiorna prima l'URL corrente in base al percorso fornito, quindi aggiorna il modello. La proprietà `window.location.origin` restituisce l'URL radice, permettendo di ricostruire un URL completo da un dato percorso.
|
||||
|
||||
Ora che si ha questa funzione, ci si può occupare del problema che si verifica se un percorso non corrisponde a nessuna rotta definita. Si modificherà la funzione `updateRoute` aggiungendo una soluzione di contingenza per indirizzare verso una delle rotte esistenti se non viene trovata una corrispondenza.
|
||||
|
||||
```js
|
||||
function updateRoute() {
|
||||
const path = window.location.pathname;
|
||||
const route = routes[path];
|
||||
|
||||
if (!route) {
|
||||
return navigate('/login');
|
||||
}
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
Se non è possibile trovare una rotta, si esegue un reindirizzamento alla pagina `login`.
|
||||
|
||||
Ora si crea una funzione per ottenere l'URL quando si fa clic su un collegamento e per impedire il comportamento predefinito del browser per un collegamento:
|
||||
|
||||
```js
|
||||
function onLinkClick(event) {
|
||||
event.preventDefault();
|
||||
navigate(event.target.href);
|
||||
}
|
||||
```
|
||||
|
||||
Si completa il sistema di navigazione aggiungendo collegamenti ai link di accesso (*Login*) e di disconnessione (*Logout*) nell'HTML.
|
||||
|
||||
```html
|
||||
<a href="/dashboard" onclick="onLinkClick(event)">Login</a>
|
||||
...
|
||||
<a href="/login" onclick="onLinkClick(event)">Logout</a>
|
||||
```
|
||||
|
||||
Utilizzando l 'attributo [`onclick`](https://developer.mozilla.org/docs/Web/API/GlobalEventHandlers/onclick) associare l'evento `click` al codice JavaScript, in questo caso la chiamata alla funzione `navigate()` .
|
||||
|
||||
Provare a fare clic su questi collegamenti, ora si dovrebbe essere in grado di navigare tra le diverse schermate dell'app.
|
||||
|
||||
✅ Il metodo `history.pushState` fa parte dello standard HTML5 e implementato in [tutti i browser moderni](https://caniuse.com/?search=pushState). Se si sta creando un'app web per browser meno recenti, c'è un trucco che si può usare al posto di questa API: usando un [hash (`#`)](https://en.wikipedia.org/wiki/URI_fragment) prima del percorso si può implementare un instradatamento che funziona con la normale navigazione dell'elemento ancora e non ricarica la pagina, poiché il suo scopo era creare collegamenti all'interno di una pagina.
|
||||
|
||||
## Gestione dei pulsanti Avanti e Indietro del browser
|
||||
|
||||
L'utilizzo di `history.pushState` crea nuove voci nella cronologia di navigazione del browser. Si può verificare tenendo premuto il *pulsante Indietro* del proprio browser, dovrebbe visualizzare qualcosa del genere:
|
||||
|
||||

|
||||
|
||||
Se si prova a fare clic sul pulsante Indietro alcune volte, si vedrà che l'URL corrente cambia e la cronologia viene aggiornata, ma lo stesso modello continua a essere visualizzato.
|
||||
|
||||
Questo perché il browser non sa che si deve chiamare `updateRoute()` ogni volta che cambia la cronologia. Se si dà un'occhiata alla documentazione di [`history.pushState`](https://developer.mozilla.org/docs/Web/API/History/pushState) si può vedere che se lo stato cambia, vale a dire che si è passati a un URL diverso, viene attivato l'[evento](https://developer.mozilla.org/docs/Web/API/Window/popstate_event) `popstate`. Verrà usato per risolvere quel problema.
|
||||
|
||||
### Attività
|
||||
|
||||
Per assicurarsi che il modello visualizzato venga aggiornato quando la cronologia del browser cambia, si aggiungerà una nuova funzione che chiama `updateRoute()`. Verrà fatto in fondo al file `app.js`:
|
||||
|
||||
```js
|
||||
window.onpopstate = () => updateRoute();
|
||||
updateRoute();
|
||||
```
|
||||
|
||||
> Nota: è stata usata una [funzione freccia](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Functions/Arrow_functions) qui per dichiarare il gestore di eventi `popstate` per concisione, ma una funzione normale andrebbe bene allo stesso modo.
|
||||
|
||||
Ecco un video di aggiornamento sulle funzioni freccia:
|
||||
|
||||
[](https://youtube.com/watch?v=OP6eEbOj2sc "")
|
||||
|
||||
> Fare clic sull'immagine sopra per un video sulle funzioni freccia
|
||||
|
||||
Ora provare a utilizzare i pulsanti Indietro e Avanti del proprio browser e controllare che il percorso visualizzato sia aggiornato correttamente questa volta.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Sfida
|
||||
|
||||
Aggiungere un nuovo modello e instradare per una terza pagina che mostra i crediti per questa app.
|
||||
|
||||
## Quiz Post-Lezione
|
||||
|
||||
[Quiz post-lezione](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/42?loc=it)
|
||||
|
||||
## Revisione e Auto Apprendimento
|
||||
|
||||
Il routing (instradamento) è una delle parti sorprendentemente complicate dello sviluppo web, soprattutto perché il web passa dai comportamenti di aggiornamento della pagina all'aggiornamento della pagina dell'applicazione a pagina singola. Leggere alcune informazioni su [come il servizio App Web Static di Azure](https://docs.microsoft.com/azure/static-web-apps/routes?WT.mc_id=academic-4621-cxa) gestisce il routing. Si può spiegare perché alcune delle decisioni descritte in quel documento sono necessarie?
|
||||
|
||||
## Compito
|
||||
|
||||
[Migliorare l'instradamento](assignment.it.md)
|
@@ -1,304 +0,0 @@
|
||||
# バンキングアプリを作ろう その 1: Web アプリの HTML テンプレートとルート
|
||||
|
||||
## レッスン前の小テスト
|
||||
|
||||
[レッスン前の小テスト](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/41?loc=ja)
|
||||
|
||||
### イントロダクション
|
||||
|
||||
ブラウザに JavaScript が登場して以来、Web サイトはこれまで以上にインタラクティブで複雑になっています。Web 技術は現在では、ブラウザに直接実行される完全に機能的なアプリケーションを作成するために一般的に使用されており、[Web アプリケーション](https://ja.wikipedia.org/wiki/%E3%82%A6%E3%82%A7%E3%83%96%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3)と呼ばれています。Web アプリケーションは高度にインタラクティブであるため、ユーザーはアクションが実行されるたびに全ページのリロードを待ちたくありません。そのため、JavaScript は DOM を使用して HTML を直接更新し、よりスムーズなユーザーエクスペリエンスを提供するために使用されます。
|
||||
|
||||
このレッスンでは、HTML テンプレートを使用して、HTML ページ全体をリロードすることなく表示・更新できる複数の画面を作成し、銀行の Web アプリを作成するための基礎を構築していきます。
|
||||
|
||||
### 前提条件
|
||||
|
||||
このレッスンで構築する Web アプリをテストするためには、ローカルの Web サーバーが必要です。もし持っていない場合は、[Node.js](https://nodejs.org/ja) をインストールして、プロジェクトフォルダから `npx lite-server` コマンドを使用してください。これでローカルの Web サーバーが作成され、ブラウザでアプリを開くことができます。
|
||||
|
||||
### 準備
|
||||
|
||||
コンピュータ上に `bank` という名前のフォルダを作成し、その中に `index.html` というファイルを作成します。この HTML [ボイラープレート](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>
|
||||
<!-- ここで作業することになります。 -->
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## HTML テンプレート
|
||||
|
||||
Web ページに複数の画面を作成したい場合、表示したい画面ごとに1つの HTML ファイルを作成するのが1つの解決策です。しかし、この方法にはいくつかの不都合があります。
|
||||
|
||||
- 画面切り替えの際に HTML 全体を再読み込みしなければならず、時間がかかることがあります
|
||||
- 画面間でデータを共有するのは難しいです
|
||||
|
||||
もう一つのアプローチは、HTML ファイルを一つだけ持ち、`<template>` 要素を使って複数の [HTML テンプレート](https://developer.mozilla.org/ja/docs/Web/HTML/Element/template)を定義することです。テンプレートはブラウザに表示されない再利用可能な HTML ブロックであり、JavaScript を使って実行時にインスタンス化する必要があります。
|
||||
|
||||
### タスク
|
||||
|
||||
ログインページとダッシュボードの 2 つの画面を持つ銀行アプリを作成します。まず、アプリの異なる画面をインスタンス化するために使用するプレースホルダ要素を HTML の body に追加します。
|
||||
|
||||
```html
|
||||
<div id="app">Loading...</div>
|
||||
```
|
||||
|
||||
JavaScript での検索が容易になるように、`id` を付与しています。
|
||||
|
||||
> ヒント: この要素の内容が置き換えられるので、アプリの読み込み中に表示される読み込みメッセージやインジケータを入れることができます。
|
||||
|
||||
次に、ログインページの HTML テンプレートを下に追加します。今のところ、私たちはそこにタイトルとナビゲーションを実行するために使用するリンクを含むセクションを置くだけです。
|
||||
|
||||
```html
|
||||
<template id="login">
|
||||
<h1>Bank App</h1>
|
||||
<section>
|
||||
<a href="/dashboard">Login</a>
|
||||
</section>
|
||||
</template>
|
||||
```
|
||||
|
||||
次に、ダッシュボードページ用に別の HTML テンプレートを追加します。このページには異なるセクションが含まれます。
|
||||
|
||||
- タイトルとログアウトリンクのあるヘッダー
|
||||
- 銀行口座の当座預金残高
|
||||
- 表に表示されるトランザクションのリスト
|
||||
|
||||
```html
|
||||
<template id="dashboard">
|
||||
<header>
|
||||
<h1>Bank App</h1>
|
||||
<a href="/login">Logout</a>
|
||||
</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>
|
||||
```
|
||||
|
||||
> ヒント: HTML テンプレートを作成する際に、どのように見えるかを確認したい場合は、`<template>` と `</template>` の行を `<!-- -->` で囲んでコメントアウトすることができます。
|
||||
|
||||
✅ なぜテンプレートに `id` 属性を使うと思いますか? クラスのような他のものを使うことはできないのでしょうか?
|
||||
|
||||
## JavaScript でテンプレートを表示する
|
||||
|
||||
現在の HTML ファイルをブラウザで試してみると、`Loading...` と表示されて動かなくなるのがわかるでしょう。これは、HTML テンプレートをインスタンス化して表示するために JavaScript コードを追加する必要があるためです。
|
||||
|
||||
テンプレートのインスタンス化は通常3つのステップで行われます。
|
||||
|
||||
1. 例えば、[`document.getElementById`](https://developer.mozilla.org/ja/docs/Web/API/Document/getElementById) を使用して、DOM 内のテンプレート要素を取得します
|
||||
2. [`cloneNode`](https://developer.mozilla.org/ja/docs/Web/API/Node/cloneNode) を使用して、テンプレート要素のクローンを作成します
|
||||
3. 例えば [`appendChild`](https://developer.mozilla.org/ja/docs/Web/API/Node/appendChild) を使用して、可視要素の下の DOM にアタッチします
|
||||
|
||||
✅ なぜ DOM にアタッチする前にテンプレートをクローンする必要があるのでしょうか? このステップをスキップしたらどうなると思いますか?
|
||||
|
||||
### タスク
|
||||
|
||||
プロジェクトフォルダに `app.js` という名前の新しいファイルを作成し、そのファイルを HTML の `<head>` セクションにインポートします。
|
||||
|
||||
```html
|
||||
<script src="app.js" defer></script>
|
||||
```
|
||||
|
||||
では、`app.js` で新しい関数 `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);
|
||||
}
|
||||
```
|
||||
|
||||
ここで行うことは、上記の3つのステップとまったく同じです。テンプレートを `templateId` という名前でインスタンス化し、そのクローンされたコンテンツをアプリのプレースホルダ内に配置します。テンプレートのサブツリー全体をコピーするには、`cloneNode(true)` を使用する必要があることに注意してください。
|
||||
|
||||
テンプレートのサブツリー全体をコピーするには、`cloneNode(true)` を使用する必要があることに注意してください。
|
||||
|
||||
```js
|
||||
updateRoute('login');
|
||||
```
|
||||
|
||||
✅ このコード `app.innerHTML = '';` の目的は何ですか?これがないとどうなるのでしょうか?
|
||||
|
||||
## ルートの作成
|
||||
|
||||
Web アプリの話をするときに、**URL** を表示すべき特定の画面にマッピングする意図を *ルーティング* と呼んでいます。複数の HTML ファイルを持つ Web サイトでは、ファイルパスが URL に反映されるため、これは自動的に行われます。たとえば、プロジェクトフォルダにこれらのファイルがあるとします。
|
||||
|
||||
```
|
||||
mywebsite/index.html
|
||||
mywebsite/login.html
|
||||
mywebsite/admin/index.html
|
||||
```
|
||||
|
||||
ルートに `mywebsite` を指定して Web サーバを作成した場合、URL のマッピングは以下のようになる。
|
||||
|
||||
```
|
||||
https://site.com --> mywebsite/index.html
|
||||
https://site.com/login.html --> mywebsite/login.html
|
||||
https://site.com/admin/ --> mywebsite/admin/index.html
|
||||
```
|
||||
|
||||
しかし、私たちの Web アプリでは、すべての画面を含む単一の HTML ファイルを使用しているので、このデフォルトの動作は役に立ちません。この map を手動で作成し、JavaScript を使用して表示されるテンプレートの更新を実行する必要があります。
|
||||
|
||||
### タスク
|
||||
|
||||
URL パスとテンプレート間の [map](https://en.wikipedia.org/wiki/Associative_array) を実装するために、シンプルなオブジェクトを使用します。このオブジェクトを `app.js` ファイルの先頭に追加します。
|
||||
|
||||
```js
|
||||
const routes = {
|
||||
'/login': { templateId: 'login' },
|
||||
'/dashboard': { templateId: 'dashboard' },
|
||||
};
|
||||
```
|
||||
|
||||
では、`updateRoute` 関数を少し修正してみましょう。引数に `templateId` を直接渡すのではなく、まず現在の URL を見て、map を使って対応するテンプレート ID の値を取得したいと思います。URL からパス部分だけを取得するには、[`window.location.pathname`](https://developer.mozilla.org/docs/Web/API/Location/pathname)を使うことができます。
|
||||
|
||||
```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);
|
||||
}
|
||||
```
|
||||
|
||||
ここでは、宣言したルートを対応するテンプレートにマッピングしてみました。ブラウザの URL を手動で変更することで正常に動作するか試してみてください。
|
||||
|
||||
✅ URL に未知のパスを入力するとどうなるでしょうか? どうすれば解決できるのでしょうか?
|
||||
|
||||
## ナビゲーションの追加
|
||||
|
||||
私たちのアプリの次のステップは、URL を手動で変更することなく、ページ間を移動する可能性を追加することです。これは2つのことを意味します。
|
||||
|
||||
1. 現在の URL を更新する
|
||||
2. 新しい URL に基づいて表示されるテンプレートを更新する
|
||||
|
||||
2番目の部分はすでに `updateRoute` 関数で処理したので、現在の URL を更新する方法を見つけなければなりません。
|
||||
|
||||
JavaScript、特に [`history.pushState`](https://developer.mozilla.org/ja/docs/Web/API/History/pushState) を使う必要があります。これは HTML をリロードせずに URL を更新して閲覧履歴に新しいエントリを作成することができます。
|
||||
|
||||
> 注: HTML アンカー要素[`<a href>`](https://developer.mozilla.org/ja/docs/Web/HTML/Element/a)は単独で使用して異なる URL へのハイパーリンクを作成することができますが、ブラウザはデフォルトで HTML をリロードさせることになります。カスタム javascript でルーティングを扱う際には、クリックイベントの preventDefault() 関数を使用して、この動作を防ぐ必要があります。
|
||||
|
||||
### タスク
|
||||
|
||||
アプリ内でナビゲートするために使用できる新しい関数を作成してみましょう。
|
||||
|
||||
```js
|
||||
function navigate(path) {
|
||||
window.history.pushState({}, path, path);
|
||||
updateRoute();
|
||||
}
|
||||
```
|
||||
|
||||
このメソッドは最初に与えられたパスに基づいて現在の URL を更新し、その後テンプレートを更新します。プロパティ `window.location.origin` は URL のルートを返すので、与えられたパスから完全な URL を再構築することができます。
|
||||
|
||||
これでこの関数ができたので、パスが定義されたルートにマッチしない場合の問題を解決することができます。一致するルートが見つからなかった場合は、既存のルートにフォールバックを追加して `updateRoute` 関数を修正する。
|
||||
|
||||
```js
|
||||
function updateRoute() {
|
||||
const path = window.location.pathname;
|
||||
const route = routes[path];
|
||||
|
||||
if (!route) {
|
||||
return navigate('/login');
|
||||
}
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
ルートが見つからない場合は、`login` ページにリダイレクトします。
|
||||
|
||||
リンクがクリックされたときに URL を取得し、ブラウザのデフォルトのリンク動作を防ぐための関数を作ってみましょう。
|
||||
|
||||
```js
|
||||
function onLinkClick(event) {
|
||||
event.preventDefault();
|
||||
navigate(event.target.href);
|
||||
}
|
||||
```
|
||||
|
||||
HTML の *Login* と *Logout* リンクにバインディングを追加してナビゲーションシステムを完成させましょう。
|
||||
|
||||
```html
|
||||
<a href="/dashboard" onclick="onLinkClick(event)">Login</a>
|
||||
...
|
||||
<a href="/login" onclick="onLinkClick(event)">Logout</a>
|
||||
```
|
||||
|
||||
[`onclick`](https://developer.mozilla.org/ja/docs/Web/API/GlobalEventHandlers/onclick) 属性を使用して、`click` イベントを JavaScript コードにバインドし、ここでは `navigate()` 関数の呼び出しを行います。
|
||||
|
||||
これらのリンクをクリックしてみると、アプリの異なる画面間を移動できるようになるはずです。
|
||||
|
||||
✅ `history.pushState` メソッドは HTML5 標準の一部であり、[すべての最新ブラウザ](https://caniuse.com/?search=pushState)で実装されています。古いブラウザ用の Web アプリを構築している場合、この API の代わりに使用できるトリックがあります。パスの前に[ハッシュ (`#`)](https://en.wikipedia.org/wiki/URI_fragment) を使用すると、通常のアンカーナビゲーションで動作し、ページを再読み込みしないルーティングを実装することができます。その目的は、ページ内に内部リンクを作成することです。
|
||||
|
||||
## ブラウザの戻るボタンと進むボタンの扱い
|
||||
|
||||
`history.pushState` を使うと、ブラウザのナビゲーション履歴に新しいエントリが作成されます。ブラウザの *戻るボタン* を押すと、以下のように表示されることを確認することができます。
|
||||
|
||||

|
||||
|
||||
何度か戻るボタンをクリックしてみると、現在の URL が変わって履歴が更新されていますが、同じテンプレートが表示され続けています。
|
||||
|
||||
これは、履歴が変わるたびに `updateRoute()` を呼び出す必要があることを知らないからです。[`history.pushState` のドキュメント](https://developer.mozilla.org/ja/docs/Web/API/History/pushState)を見てみると、状態が変化した場合、つまり別の URL に移動した場合には、[`popstate`](https://developer.mozilla.org/docs/Web/API/Window/popstate_event)イベントが発生することがわかります。これを使ってこの問題を解決しましょう。
|
||||
|
||||
### タスク
|
||||
|
||||
ブラウザの履歴が変更されたときに表示されるテンプレートが更新されるようにするために、`updateRoute()` を呼び出す新しい関数をアタッチします。これは `app.js` ファイルの下部で行います。
|
||||
|
||||
```js
|
||||
window.onpopstate = () => updateRoute();
|
||||
updateRoute();
|
||||
```
|
||||
|
||||
> 注: ここでは簡潔さのために `popstate` イベントハンドラを宣言するために [アロー関数](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions/Arrow_functions) を使用していますが、通常の関数でも同じように動作します。
|
||||
|
||||
これは、アロー関数についてのリフレッシュビデオです。
|
||||
|
||||
[](https://youtube.com/watch?v=OP6eEbOj2sc "Arrow Functions")
|
||||
|
||||
今度はブラウザの戻るボタンと進むボタンを使ってみて、今回表示されたルートが正しく更新されているかどうかを確認してみてください。
|
||||
|
||||
---
|
||||
|
||||
## 🚀 チャレンジ
|
||||
|
||||
このアプリのクレジットを表示する3ページ目のテンプレートとルートを追加します。
|
||||
|
||||
## レッスン後の小テスト
|
||||
|
||||
[レッスン後の小テスト](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/42?loc=ja)
|
||||
|
||||
## 復習と自己学習
|
||||
|
||||
ルーティングは Web 開発の驚くほどトリッキーな部分の1つで、特に Web がページ更新の動作からシングルページアプリケーションのページ更新へと移行するにつれ、そのような部分が増えてきています。[Azure Static Web Apps プレビューでのルート](https://docs.microsoft.com/ja-jp/azure/static-web-apps/routes/?WT.mc_id=academic-77807-sagibbon)がルーティングを扱うことについて少し読んでみてください。そのドキュメントに記載されているいくつかの決定が必要な理由を説明できますか?
|
||||
|
||||
## 課題
|
||||
|
||||
[ルーティングの改善](assignment.ja.md)
|
@@ -1,295 +0,0 @@
|
||||
# 은행 앱 제작하기 파트 1: 웹 앱의 HTML 템플릿과 라우터
|
||||
|
||||
## 강의 전 퀴즈
|
||||
|
||||
[Pre-lecture quiz](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/41?loc=ko)
|
||||
|
||||
### 소개
|
||||
|
||||
브라우저에 JavaScript가 등장한 이후, 웹 사이트는 그 어느 순간보다 상호 작용하며 복잡해지고 있습니다. 웹 기술은 일반적으로 [web applications](https://en.wikipedia.org/wiki/Web_application)라고 불리는 브라우저로 직접 실행되는 완전한 기능의 애플리케이션을 만들 때 사용됩니다. 웹 앱은 매우 대화형이므로, 사용자는 작업되는 순간에 전체 페이지가 다시 불러오며 기다리는 것을 원치 않습니다. 원활한 사용자 경험을 제공하기 위해, JavaScript로 DOM을 사용하여 HTML을 직접 갱신합니다.
|
||||
|
||||
이번 강의에서는, 전체 HTML 페이지를 다시 불러오지 않으면서 출력하고 갱신할 여러 화면을 만들기 위해 HTML 템플릿을 사용할 것이므로, 은행 웹 앱을 만들기 위한 기초를 레이아웃합니다.
|
||||
|
||||
### 준비물
|
||||
|
||||
이 강의에서 만들 웹 앱을 테스트하려면 로컬 웹 서버가 필요합니다. 없는 경우에는, [Node.js](https://nodejs.org)를 설치하고 프로젝트 폴더에서 `npx lite-server` 명령을 수행할 수 있습니다. 로컬 웹 서버를 만들고 브라우저에서 앱을 엽니다.
|
||||
|
||||
### 준비
|
||||
|
||||
컴퓨터에서, `index.html` 파일이 있는 `bank`라는 폴더를 만듭니다. 이 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 템플릿
|
||||
|
||||
웹 페이지에 여러 화면을 만드려는 경우에는, 하나의 솔루션은 출력하려는 모든 화면에 대해 각자 HTML 파일을 만드는 것입니다. 그러나, 이 솔루션에는 몇 가지 불편한 점이 있습니다:
|
||||
|
||||
- 화면 전환 시 전체 HTML을 다시 불러와야 하므로, 속도가 느릴 수 있습니다.
|
||||
- 서로 다른 화면 간 데이터 공유가 어렵습니다.
|
||||
|
||||
또 다른 방법은 HTML 파일이 하나일 때, `<template>` 요소로 여러 [HTML templates](https://developer.mozilla.org/docs/Web/HTML/Element/template)을 정의하는 것입니다. 템플릿은 브라우저에 보이지 않는 재사용 가능한 HTML 블록이면서, JavaScript를 사용해서 런타임에 인스턴스화합니다.
|
||||
|
||||
### 작업
|
||||
|
||||
두 화면이 있는 은행 앱을 만들 것입니다: 로그인 페이지와 대시보드. 먼저, 앱의 다양한 화면을 인스턴스화할 때 사용할 placeholder 요소를 HTML 본문에 추가하겠습니다:
|
||||
|
||||
```html
|
||||
<div id="app">Loading...</div>
|
||||
```
|
||||
|
||||
나중에 JavaScript로 쉽게 찾도록 `id`를 제공합니다.
|
||||
|
||||
> Tip: 이 요소의 내용은 바뀌므로, 앱이 불러와지는 동안 보여지는 로딩 메시지 또는 인디케이터를 넣을 수 있습니다.
|
||||
|
||||
다음은, 로그인 페이지 HTML 템플릿 아래에 추가하겠습니다. 지금은 탐색하며 사용할 버튼이 포함된 제목과 섹션만 여기에 넣겠습니다.
|
||||
|
||||
```html
|
||||
<template id="login">
|
||||
<h1>Bank App</h1>
|
||||
<section>
|
||||
<button>Login</button>
|
||||
</section>
|
||||
</template>
|
||||
```
|
||||
|
||||
그러고 대시보드 페이지에 대한 다른 HTML 템플릿을 추가합니다. 이 페이지에는 다른 섹션도 포함됩니다:
|
||||
|
||||
- 제목과 로그아웃 버튼이 있는 헤더
|
||||
- 은행 계정의 현재 잔액
|
||||
- 테이블에 표시된, 트랜잭션 목록
|
||||
|
||||
```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: HTML 템플릿을 만들 때, 모양을 확인하려면, `<!-->`로 묶어 `<template>` 혹은 `</template>` 줄을 주석 처리할 수 있습니다.
|
||||
|
||||
✅ 템플릿에 `id` 속성을 사용하는 이유는 무엇일까요? 강의처럼 다른 것을 쓸 수 있나요?
|
||||
|
||||
## JavaScript로 템플릿 출력하기
|
||||
|
||||
브라우저에서 현재 HTML 파일을 시도하면, `Loading...`이 출력되는 것을 볼 수 있습니다. HTML 템플릿을 인스턴스화하고 출력하기 위해 JavaScript 코드를 추가했기 때문입니다.
|
||||
|
||||
템플릿 인스턴스화는 일반적으로 3 단계로 진행됩니다:
|
||||
|
||||
1. [`document.getElementById`](https://developer.mozilla.org/docs/Web/API/Document/getElementById)를 사용한 예시로, DOM에서 템플릿 요소 검색합니다.
|
||||
2. [`cloneNode`](https://developer.mozilla.org/docs/Web/API/Node/cloneNode)로, 템플릿 요소를 복제합니다.
|
||||
3. [`appendChild`](https://developer.mozilla.org/docs/Web/API/Node/appendChild)를 사용한 예시로, 보이는 요소 아래의 DOM에 붙입니다.
|
||||
|
||||
✅ DOM에 붙이기 전에 템플릿을 복제해야하는 이유는 무엇일까요? 이 단계를 넘기면 어떻게 될까요?
|
||||
|
||||
### 작업
|
||||
|
||||
프로젝트 폴더에 `app.js`라는 새 파일을 만들고 HTML의 `<head>` 섹션에서 해당 파일을 가져옵니다:
|
||||
|
||||
```html
|
||||
<script src="app.js" defer></script>
|
||||
```
|
||||
|
||||
이제 `app.js`에서, 새로운 함수인 `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);
|
||||
}
|
||||
```
|
||||
|
||||
우리가 하는 일은 정확히 위에서 설명한 3단계입니다. id `templateId`로 템플릿을 인스턴스화하고, 복제된 콘텐츠를 앱 placeholder에 넣습니다. 템플릿의 전체 하위 트리를 복사하려면 `cloneNode(true)`로 사용해야 합니다.
|
||||
|
||||
이제 템플릿 중 하나를 사용하여 이 함수를 호출하고 결과를 봅니다.
|
||||
|
||||
```js
|
||||
updateRoute('login');
|
||||
```
|
||||
|
||||
✅ 이 `app.innerHTML = '';` 코드의 목적은 무엇인가요? 없다면 어떻게 될까요?
|
||||
|
||||
## 라우터 생성하기
|
||||
|
||||
웹 앱에 대해 이야기할 때, **URLs**을 보여주기 위해 특정 화면에 매핑하려는 의도를 *Routing*이라고 합니다. 여러 HTML 파일에, 웹 사이트에서 파일 경로가 URL에 반영되므로 자동으로 수행됩니다. 예를 들면, 프로젝트 폴더에 다음 파일이 있습니다:
|
||||
|
||||
```
|
||||
mywebsite/index.html
|
||||
mywebsite/login.html
|
||||
mywebsite/admin/index.html
|
||||
```
|
||||
|
||||
만약 상위에 `mywebsite`로 웹 서버를 생성하면, URL 맵핑은 이렇게 이루어집니다:
|
||||
|
||||
```
|
||||
https://site.com --> mywebsite/index.html
|
||||
https://site.com/login.html --> mywebsite/login.html
|
||||
https://site.com/admin/ --> mywebsite/admin/index.html
|
||||
```
|
||||
|
||||
그러나, 웹 앱이라면 모든 화면이 포함된 단일 HTML 파일을 사용하므로 이러한 기본 동작은 도와주지 못합니다. 이 맵을 수동으로 만들고 JavaScript로 출력되는 템플릿을 갱신해야 합니다.
|
||||
|
||||
### 작업
|
||||
|
||||
간단한 객체로 URL 경로와 템플릿 사이에서 [map](https://en.wikipedia.org/wiki/Associative_array)을 구현합니다. `app.js` 파일의 상단에 객체를 추가합니다.
|
||||
|
||||
```js
|
||||
const routes = {
|
||||
'/login': { templateId: 'login' },
|
||||
'/dashboard': { templateId: 'dashboard' },
|
||||
};
|
||||
```
|
||||
|
||||
이제 `updateRoute` 함수를 약간 수정합니다. `templateId`를 인수로 직접 주는 대신, 먼저 현재 URL을 보고 찾은 다음, 맵을 사용하여 해당 템플릿 ID 값을 가져오려 합니다. [`window.location.pathname`](https://developer.mozilla.org/docs/Web/API/Location/pathname)으로 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);
|
||||
}
|
||||
```
|
||||
|
||||
여기에서 선언한 라우터를 해당 템플릿에 매핑했습니다. 브라우저에서 수동으로 URL을 변경하여 잘 작동하는지 볼 수 있습니다.
|
||||
|
||||
✅ URL에 알 수 없는 경로를 입력하면 어떤 일이 벌어지나요? 어떻게 해결할 수 있나요?
|
||||
|
||||
## 네비게이션 추가하기
|
||||
|
||||
앱의 다음 단계는 URL을 수동으로 안 바꾸고 페이지 사이를 이동할 수 있도록 추가하는 것입니다. 이는 두 가지를 의미합니다:
|
||||
|
||||
1. 현재 URL로 갱신하기
|
||||
2. 새로운 URL를 기반으로 출력된 템플릿 갱신하기
|
||||
|
||||
두 번째 부분은 `updateRoute` 함수로 이미 처리했으므로, 현재 URL로 갱신하는 방법을 알아냅니다.
|
||||
|
||||
HTML 앵커 요소 [`<a>`](https://developer.mozilla.org/docs/Web/HTML/Element/a)를 사용하여 다른 URL에 대한 하이퍼링크를 만들 수 있지만, 여기에서 사용하면 브라우저가 HTML을 다시 불러오게 됩니다.
|
||||
|
||||
대신 URL을 업데이트 할 수 있는 JavaScript와 더 구체적으로 [`history.pushState`](https://developer.mozilla.org/docs/Web/API/History/pushState)를 사용해야합니다. HTML을 다시 불러오지 않고 검색 기록에 새로운 항목을 만듭니다.
|
||||
|
||||
### 작업
|
||||
|
||||
앱에서 탐색할 때 사용할 수 있는 새로운 함수를 만들어 보겠습니다:
|
||||
|
||||
```js
|
||||
function navigate(path) {
|
||||
window.history.pushState({}, path, path);
|
||||
updateRoute();
|
||||
}
|
||||
```
|
||||
|
||||
이 메소드는 먼저 주어진 경로에 따라 현재 URL을 갱신한 다음에, 템플릿을 업데이트합니다. `window.location.origin` 속성은 URL 최상위를 반환하므로, 주어진 경로에서 전체 URL을 다시 구성할 수 있습니다.
|
||||
|
||||
이제 함수가 있으므로, 경로가 정의된 라우터와 일치하지 않는 경우에 발생할 문제를 해결할 수 있습니다. 일치하는 경로를 찾을 수 없는 경우에는 기존 경로 중 하나에 fallback로 추가하고자 `updateRoute` 함수를 수정합니다.
|
||||
|
||||
```js
|
||||
function updateRoute() {
|
||||
const path = window.location.pathname;
|
||||
const route = routes[path];
|
||||
|
||||
if (!route) {
|
||||
return navigate('/login');
|
||||
}
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
만약 라우터를 찾지 못한다면, `login` 페이지로 리다이렉트됩니다.
|
||||
|
||||
HTML의 *Login*과 *Logout* 버튼에 바인딩을 추가하여 내비게이션 시스템을 완성해봅니다.
|
||||
|
||||
```html
|
||||
<button onclick="navigate('/dashboard')">Login</button>
|
||||
...
|
||||
<button onclick="navigate('/login')">Logout</button>
|
||||
```
|
||||
|
||||
[`onclick`](https://developer.mozilla.org/docs/Web/API/GlobalEventHandlers/onclick) 속성을 사용하여 `click` 이벤트를 JavaScript 코드로 바인딩합니다, 여기에서 `navigate()` 함수를 호출합니다.
|
||||
|
||||
이 버튼들을 클릭해보세요, 이제 앱의 여러 화면들을 이동할 수 있습니다.
|
||||
|
||||
✅ `history.pushState` 메소드는 HTML5 표준의 일부이며 [모든 모던 브라우저](https://caniuse.com/?search=pushState)에서 구현됩니다. 옛날 브라우저의 웹 앱을 제작하는 경우, 이 API 대신 사용할 수 있는 트릭이 있습니다: 경로 앞에 [hash (`#`)](https://en.wikipedia.org/wiki/URI_fragment)를 사용한다면 일반 앵커 탐색처럼 동작하면서 페이지를 다시 안 불러오는 라우팅을 구현할 수 있습니다, 페이지 내에 내부 링크를 만드는 것이 목적입니다.
|
||||
|
||||
## 브라우저의 뒤로가기와 앞으로가기 버튼 제어하기
|
||||
|
||||
`history.pushState`를 사용하면 브라우저의 탐색 기록에 새로운 항목이 생성됩니다. 브라우저의 *back button*을 누르고 있으면 다음처럼 내용이 표시되는지 볼 수 있습니다:
|
||||
|
||||

|
||||
|
||||
뒤로가기 버튼을 몇 번 클릭하면, 현재 URL이 변경되며 히스토리가 갱신되지만 동일한 템플릿이 계속 출력되는 것을 볼 수 있습니다.
|
||||
|
||||
히스토리가 바뀔 때마다 `updateRoute()`를 호출해야 한다는 사실을 모르기 때문입니다. [`history.pushState` documentation](https://developer.mozilla.org/docs/Web/API/History/pushState)을 살펴보면, 상태가 바뀌는 지 확인할 수 있습니다 - 다른 URL로 이동했다고 의미합니다. - [`popstate`](https://developer.mozilla.org/docs/Web/API/Window/popstate_event) 이벤트가 연결됩니다. 이 이슈를 해결하는 데 사용할 것입니다.
|
||||
|
||||
### 작업
|
||||
|
||||
브라우저 히스토리가 바뀔 때마다 출력된 템플릿을 갱신하도록 `updateRoute()`를 호출하는 새 함수를 붙입니다. `app.js` 파일 하단에서 작업합니다:
|
||||
|
||||
```js
|
||||
window.onpopstate = () => updateRoute();
|
||||
updateRoute();
|
||||
```
|
||||
|
||||
> Note: 여기서는 간결함을 위해 [arrow function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Functions/Arrow_functions)을 사용하여 `popstate` 이벤트 핸들러를 선언했지만, 일반적인 함수와 동일하게 작동합니다.
|
||||
|
||||
다음은 화살표 함수에 대한 복습 동영상입니다:
|
||||
|
||||
[](https://youtube.com/watch?v=OP6eEbOj2sc "Arrow Functions")
|
||||
|
||||
이제 브라우저의 뒤로가기와 앞으로가기 버튼을 사용해보세요, 그리고 이 순간마다 올바르게 갱신되어 출력되는 지에 대하여 확인합니다.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 도전
|
||||
|
||||
이 앱의 크레딧을 보여주는 세 번째 페이지에 새로운 템플릿과 라우터를 추가합니다.
|
||||
|
||||
## 강의 후 퀴즈
|
||||
|
||||
[Post-lecture quiz](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/42?loc=ko)
|
||||
|
||||
## 리뷰 & 자기주도 학습
|
||||
|
||||
라우팅은 웹 개발의 놀랍고 까다로운 부분 중 하나입니다, 특히 웹의 페이지 새로고침 동작에서 단일 페이지 애플리케이션 페이지 새로고침으로 이동함에 따라 더욱 더 그렇습니다. [how the Azure Static Web App service](https://docs.microsoft.com/azure/static-web-apps/routes/?WT.mc_id=academic-77807-sagibbon)의 라우터 제어에 대해 약간 봅니다. 그 문서에 기술된 몇 가지 결정이 필요한 이유를 설명할 수 있나요?
|
||||
|
||||
## 과제
|
||||
|
||||
[Improve the routing](../assignment.md)
|
@@ -1,306 +0,0 @@
|
||||
# Bina Aplikasi Perbankan Bahagian 1: Templat dan Laluan HTML dalam Aplikasi Web
|
||||
|
||||
## Kuiz Pra Kuliah
|
||||
|
||||
[Kuiz Pra Kuliah](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/41)
|
||||
|
||||
### Arahan
|
||||
|
||||
Sejak munculnya JavaScript di penyemak imbas, laman web menjadi lebih interaktif dan kompleks daripada sebelumnya. Teknologi web kini biasa digunakan untuk membuat aplikasi berfungsi sepenuhnya yang langsung masuk ke penyemak imbas yang kita panggil [aplikasi web](https://en.wikipedia.org/wiki/Web_application). Oleh kerana aplikasi Web sangat interaktif, pengguna tidak mahu menunggu muat semula halaman penuh setiap kali tindakan dilakukan. Itulah sebabnya JavaScript digunakan untuk mengemas kini HTML secara langsung menggunakan DOM, untuk memberikan pengalaman pengguna yang lebih lancar.
|
||||
|
||||
Dalam pelajaran ini, kita akan meletakkan dasar untuk membuat aplikasi web bank, menggunakan templat HTML untuk membuat banyak layar yang dapat ditampilkan dan diperbarui tanpa harus memuat ulang seluruh halaman HTML.
|
||||
|
||||
### Prasyarat
|
||||
|
||||
Anda memerlukan pelayan web tempatan untuk menguji aplikasi web yang akan kami bina dalam pelajaran ini. Sekiranya tidak memilikinya, anda boleh memasang [Node.js](https://nodejs.org) dan menggunakan arahan `npx lite-server` dari folder projek anda. Ini akan membuat pelayan web tempatan dan membuka aplikasi anda di penyemak imbas.
|
||||
|
||||
### Penyediaan
|
||||
|
||||
Di komputer anda, buat folder bernama `bank` dengan fail bernama `index.html` di dalamnya. Kami akan bermula dari 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>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Templat HTML
|
||||
|
||||
Jika anda ingin membuat beberapa layar untuk halaman web, satu penyelesaiannya adalah dengan membuat satu file HTML untuk setiap layar yang ingin anda tampilkan. Walau bagaimanapun, penyelesaian ini disertakan dengan beberapa kesulitan:
|
||||
|
||||
- Anda harus memuat semula keseluruhan HTML ketika menukar skrin, yang boleh menjadi lambat.
|
||||
- Sukar untuk berkongsi data antara skrin yang berbeza.
|
||||
|
||||
Pendekatan lain hanya mempunyai satu fail HTML, dan menentukan beberapa [templat HTML](https://developer.mozilla.org/docs/Web/HTML/Element/template) menggunakan elemen `<template>`. Templat adalah blok HTML yang dapat digunakan kembali yang tidak ditampilkan oleh penyemak imbas, dan perlu dibuat pada waktu berjalan menggunakan JavaScript.
|
||||
|
||||
### Tugas
|
||||
|
||||
Kami akan membuat aplikasi bank dengan dua skrin: halaman log masuk dan papan pemuka. Pertama, mari kita tambahkan elemen penanda tempat pada badan HTML yang akan kita gunakan untuk menunjukkan pelbagai skrin aplikasi kita:
|
||||
|
||||
```html
|
||||
<div id="app">Loading...</div>
|
||||
```
|
||||
|
||||
Kami memberikannya `id` untuk memudahkan pencarian dengan JavaScript kemudian.
|
||||
|
||||
> Petua: kerana kandungan elemen ini akan diganti, kita dapat memasukkan pesan pemuatan atau penunjuk yang akan ditunjukkan semasa aplikasi dimuat.
|
||||
|
||||
Seterusnya, mari kita tambahkan di bawah templat HTML untuk halaman log masuk. Buat masa ini kami hanya akan memasukkan tajuk dan bahagian yang mengandungi pautan yang akan kami gunakan untuk melakukan navigasi.
|
||||
|
||||
```html
|
||||
<template id="login">
|
||||
<h1>Bank App</h1>
|
||||
<section>
|
||||
<a href="/dashboard">Login</a>
|
||||
</section>
|
||||
</template>
|
||||
```
|
||||
|
||||
Kemudian kami akan menambahkan templat HTML lain untuk halaman papan pemuka. Halaman ini akan mengandungi bahagian yang berbeza:
|
||||
|
||||
- Tajuk dengan tajuk dan pautan log keluar
|
||||
- Baki semasa akaun bank
|
||||
- Senarai urus niaga, ditunjukkan dalam jadual
|
||||
|
||||
```html
|
||||
<template id="dashboard">
|
||||
<header>
|
||||
<h1>Bank App</h1>
|
||||
<a href="/login">Logout</a>
|
||||
</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>
|
||||
```
|
||||
|
||||
> Petua: semasa membuat templat HTML, jika anda ingin melihat seperti apa, anda boleh memberi komen baris `<template>` `dan` `</template>` dengan melampirkannya dengan `<! - -> ` .
|
||||
|
||||
✅ Menurut anda mengapa kami menggunakan atribut `id` pada templat? Bolehkah kita menggunakan sesuatu yang lain seperti kelas?
|
||||
|
||||
## Memaparkan templat dengan JavaScript
|
||||
|
||||
Sekiranya anda mencuba fail HTML semasa anda dalam penyemak imbas, anda akan melihat fail tersebut macet memaparkan `Memuat ... '. Itu kerana kita perlu menambahkan beberapa kod JavaScript untuk memberi contoh dan memaparkan templat HTML.
|
||||
|
||||
Membuat templat biasanya dilakukan dalam 3 langkah:
|
||||
|
||||
1. Dapatkan semula elemen templat di DOM, misalnya menggunakan [`document.getElementById`](https://developer.mozilla.org/docs/Web/API/Document/getElementById).
|
||||
2. Klon elemen templat, menggunakan [`cloneNode`](https://developer.mozilla.org/docs/Web/API/Node/cloneNode).
|
||||
3. Lampirkan ke DOM di bawah elemen yang kelihatan, misalnya menggunakan [`appendChild`](https://developer.mozilla.org/docs/Web/API/Node/appendChild).
|
||||
|
||||
✅ Mengapa kita perlu mengklon templat sebelum melampirkannya ke DOM? Apa yang anda fikir akan berlaku sekiranya kita melangkau langkah ini?
|
||||
|
||||
### Tugas
|
||||
|
||||
Buat fail baru bernama `app.js` di folder projek anda dan import fail itu di bahagian `<head>` HTML anda:
|
||||
|
||||
```html
|
||||
<script src="app.js" defer></script>
|
||||
```
|
||||
|
||||
Sekarang di `app.js`, kami akan membuat fungsi baru `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);
|
||||
}
|
||||
```
|
||||
|
||||
Apa yang kami lakukan di sini adalah tepat 3 langkah yang dinyatakan di atas. Kami membuat templat dengan id `templateId`, dan memasukkan kandungan klonnya ke dalam ruang letak aplikasi kami. Perhatikan bahawa kita perlu menggunakan `cloneNode(true)` untuk menyalin keseluruhan subtree templat.
|
||||
|
||||
Sekarang panggil fungsi ini dengan salah satu templat dan lihat hasilnya.
|
||||
|
||||
```js
|
||||
updateRoute('login');
|
||||
```
|
||||
|
||||
✅ Apa tujuan kod ini `app.innerHTML = '';`? Apa yang berlaku tanpanya?
|
||||
|
||||
## Membuat laluan
|
||||
|
||||
Ketika berbicara tentang aplikasi web, kami memanggil *Routing* niat untuk memetakan **URL** ke layar tertentu yang harus ditampilkan. Di laman web dengan banyak fail HTML, ini dilakukan secara automatik kerana jalur fail ditunjukkan pada URL. Contohnya, dengan fail ini dalam folder projek anda:
|
||||
|
||||
```
|
||||
mywebsite/index.html
|
||||
mywebsite/login.html
|
||||
mywebsite/admin/index.html
|
||||
```
|
||||
|
||||
Sekiranya anda membuat pelayan web dengan `mywebsite` sebagai root, pemetaan URL akan:
|
||||
|
||||
```
|
||||
https://site.com --> mywebsite/index.html
|
||||
https://site.com/login.html --> mywebsite/login.html
|
||||
https://site.com/admin/ --> mywebsite/admin/index.html
|
||||
```
|
||||
|
||||
Walau bagaimanapun, untuk aplikasi web kami menggunakan satu fail HTML yang mengandungi semua skrin sehingga tingkah laku lalai ini tidak akan membantu kami. Kita harus membuat peta ini secara manual dan melakukan kemas kini templat yang dipaparkan menggunakan JavaScript.
|
||||
|
||||
### Tugas
|
||||
|
||||
Kami akan menggunakan objek sederhana untuk melaksanakan [peta](https://en.wikipedia.org/wiki/Associative_array) antara jalur URL dan templat kami. Tambahkan objek ini di bahagian atas fail `app.js` anda.
|
||||
|
||||
```js
|
||||
const routes = {
|
||||
'/login': { templateId: 'login' },
|
||||
'/dashboard': { templateId: 'dashboard' },
|
||||
};
|
||||
```
|
||||
|
||||
Sekarang mari kita ubah sedikit fungsi `updateRoute`. Daripada meneruskan secara langsung `templateId` sebagai argumen, kami ingin mendapatkannya dengan terlebih dahulu melihat URL semasa, dan kemudian menggunakan peta kami untuk mendapatkan nilai ID templat yang sesuai. Kita boleh menggunakan [`window.location.pathname`](https://developer.mozilla.org/docs/Web/API/Location/pathname) untuk mendapatkan hanya bahagian jalan dari 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);
|
||||
}
|
||||
```
|
||||
|
||||
Di sini kami memetakan laluan yang kami nyatakan ke templat yang sesuai. Anda boleh mencubanya agar berfungsi dengan betul dengan menukar URL secara manual di penyemak imbas anda.
|
||||
|
||||
✅ Apa yang berlaku jika anda memasukkan jalan yang tidak diketahui dalam URL? Bagaimana kita dapat menyelesaikannya?
|
||||
|
||||
## Tambahkan navigasi
|
||||
|
||||
Langkah seterusnya untuk aplikasi kita adalah menambahkan kemungkinan untuk menavigasi antara halaman tanpa perlu mengubah URL secara manual. Ini menunjukkan dua perkara:
|
||||
|
||||
1. Mengemas kini URL semasa
|
||||
2. Mengemas kini templat yang dipaparkan berdasarkan URL baru
|
||||
|
||||
Kami sudah mengurus bahagian kedua dengan fungsi `updateRoute`, jadi kami harus memikirkan cara mengemas kini URL semasa.
|
||||
|
||||
Kita mesti menggunakan JavaScript dan lebih khusus lagi [`history.pushState`](https://developer.mozilla.org/docs/Web/API/History/pushState) yang memungkinkan untuk mengemas kini URL dan buat entri baru dalam sejarah penyemakan imbas, tanpa memuatkan semula HTML.
|
||||
|
||||
> Catatan: Walaupun elemen anchor HTML [`<a href>`](https://developer.mozilla.org/docs/Web/HTML/Element/a) dapat digunakan sendiri untuk membuat hyperlink ke URL yang berbeza, ini akan menjadikan penyemak imbas memuat semula HTML secara lalai. Adalah perlu untuk mencegah tingkah laku ini ketika menangani routing dengan javascript khusus, menggunakan fungsi preventDefault () pada peristiwa klik.
|
||||
|
||||
### Tugas
|
||||
|
||||
Mari buat fungsi baru yang dapat kita gunakan untuk menavigasi di aplikasi kita:
|
||||
|
||||
```js
|
||||
function navigate(path) {
|
||||
window.history.pushState({}, path, path);
|
||||
updateRoute();
|
||||
}
|
||||
```
|
||||
|
||||
Kaedah ini terlebih dahulu mengemas kini URL semasa berdasarkan jalan yang diberikan, kemudian mengemas kini templat. Properti `window.location.origin` mengembalikan akar URL, yang membolehkan kami membina semula URL lengkap dari jalan yang ditentukan.
|
||||
|
||||
Sekarang kita mempunyai fungsi ini, kita dapat mengatasi masalah yang kita ada jika jalan tidak sesuai dengan rute yang ditentukan. Kami akan mengubah fungsi `updateRoute` dengan menambahkan fallback ke salah satu laluan yang ada jika kami tidak dapat mencari yang sesuai.
|
||||
|
||||
```js
|
||||
function updateRoute() {
|
||||
const path = window.location.pathname;
|
||||
const route = routes[path];
|
||||
|
||||
if (!route) {
|
||||
return navigate('/login');
|
||||
}
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
Sekiranya laluan tidak dapat dijumpai, kami sekarang akan mengalihkan ke halaman `login`.
|
||||
|
||||
Sekarang mari kita buat fungsi untuk mendapatkan URL apabila pautan diklik, dan untuk mengelakkan tingkah laku pautan lalai penyemak imbas:
|
||||
|
||||
```js
|
||||
function onLinkClick(event) {
|
||||
event.preventDefault();
|
||||
navigate(event.target.href);
|
||||
}
|
||||
```
|
||||
|
||||
Mari lengkapkan sistem navigasi dengan menambahkan pengikatan pada pautan *Login* dan *Logout* kami dalam HTML.
|
||||
|
||||
```html
|
||||
<a href="/dashboard" onclick="onLinkClick(event)">Login</a>
|
||||
...
|
||||
<a href="/login" onclick="onLinkClick(event)">Logout</a>
|
||||
```
|
||||
|
||||
Menggunakan atribut [`onclick`](https://developer.mozilla.org/docs/Web/API/GlobalEventHandlers/onclick) mengikat peristiwa`klik` ke kod JavaScript, di sini panggilan ke `navigasi() `fungsi.
|
||||
|
||||
Cuba klik pada pautan-pautan ini, kini anda seharusnya dapat menavigasi antara pelbagai skrin aplikasi anda.
|
||||
|
||||
✅ Kaedah `history.pushState` adalah sebahagian daripada standard HTML5 dan dilaksanakan di [semua penyemak imbas moden](https://caniuse.com/?search=pushState). Sekiranya anda membuat aplikasi web untuk penyemak imbas yang lebih lama, ada helah yang dapat Anda gunakan sebagai ganti API ini: menggunakan [hash (`#`)](https://en.wikipedia.org/wiki/URI_fragment) sebelum jalan yang dapat anda laksanakan perutean yang berfungsi dengan navigasi jangkar biasa dan tidak memuat semula halaman, kerana tujuannya adalah untuk membuat pautan dalaman dalam halaman.
|
||||
|
||||
## Mengendalikan butang belakang dan hadapan penyemak imbas
|
||||
|
||||
Menggunakan `history.pushState` membuat entri baru dalam sejarah navigasi penyemak imbas. Anda boleh memastikan bahawa dengan menahan * butang belakang * penyemak imbas anda, ia akan memaparkan sesuatu seperti ini:
|
||||
|
||||

|
||||
|
||||
Sekiranya anda cuba mengklik butang kembali beberapa kali, anda akan melihat bahawa URL semasa berubah dan sejarahnya dikemas kini, tetapi templat yang sama terus dipaparkan.
|
||||
|
||||
Ini kerana tidak tahu bahawa kita perlu memanggil `updateRoute()` setiap kali sejarah berubah. Sekiranya anda melihat dokumentasi [`history.pushState`](https://developer.mozilla.org/docs/Web/API/History/pushState), anda dapat melihat bahawa jika keadaan berubah - yang bermaksud bahawa kami berpindah ke URL yang berbeza - peristiwa [`popstate`](https://developer.mozilla.org/docs/Web/API/Window/popstate_event) dicetuskan. Kami akan menggunakannya untuk menyelesaikan masalah itu.
|
||||
|
||||
### Tugas
|
||||
|
||||
Untuk memastikan templat yang ditampilkan diperbaharui ketika sejarah penyemak imbas berubah, kami akan melampirkan fungsi baru yang memanggil `updateRoute ()`. Kami akan melakukannya di bahagian bawah fail `app.js` kami:
|
||||
|
||||
```js
|
||||
window.onpopstate = () => updateRoute();
|
||||
updateRoute();
|
||||
```
|
||||
|
||||
> Catatan: kami menggunakan [fungsi panah](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Functions/Arrow_functions) di sini untuk menyatakan pengendali acara `popstate` kami untuk kesimpulan, fungsi biasa akan berfungsi sama.
|
||||
|
||||
Berikut adalah video penyegaran mengenai fungsi anak panah:
|
||||
|
||||
[](https://youtube.com/watch?v=OP6eEbOj2sc "Fungsi Anak Panah")
|
||||
|
||||
> Klik gambar di atas untuk video mengenai fungsi anak panah.
|
||||
|
||||
Sekarang cuba gunakan butang belakang dan depan penyemak imbas anda, dan periksa bahawa laluan yang dipaparkan dikemas kini dengan betul kali ini.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Cabaran
|
||||
|
||||
Tambahkan templat dan laluan baru untuk halaman ketiga yang menunjukkan kredit untuk aplikasi ini.
|
||||
|
||||
## Kuiz Pasca Kuliah
|
||||
|
||||
[Kuiz Pasca Kuliah](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/42)
|
||||
|
||||
## Mengkaji & Belajar Sendiri
|
||||
|
||||
Perutean adalah salah satu bahagian pengembangan web yang sangat mengejutkan, terutama ketika web beralih dari tingkah laku penyegaran halaman ke penyegaran halaman Aplikasi Halaman Tunggal. Baca sedikit mengenai [bagaimana perkhidmatan Aplikasi Web Statik Azure](https://docs.microsoft.com/azure/static-web-apps/routes/?WT.mc_id=academic-77807-sagibbon) menangani penghalaan . Bolehkah anda menjelaskan mengapa beberapa keputusan yang dijelaskan pada dokumen itu diperlukan?
|
||||
|
||||
## Tugasan
|
||||
|
||||
[Tingkatkan penghalaan](assignment.ms.md)
|
@@ -1,307 +0,0 @@
|
||||
# 建立銀行網頁應用程式 Part 1:HTML 模板與網頁路由
|
||||
|
||||
## 課前測驗
|
||||
|
||||
[課前測驗](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/41?loc=zh_tw)
|
||||
|
||||
### 大綱
|
||||
|
||||
自從 JavaScript 出現在瀏覽器後,網頁開始變得更複雜、更多互動。網頁技術已經普遍地用於建立功能齊全的應用程式,執行在瀏覽器上,我們稱之為[網頁應用程式](https://zh.wikipedia.org/zh-tw/%E7%BD%91%E7%BB%9C%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F)。基於網頁應用程式的高互動性,使用者不會想在互動後做所有頁面載入所需的等待。這也是為什麼 JavaScript 使用 DOM 來更新 HTML,提供使用者更流暢的網頁體驗。
|
||||
|
||||
在這堂課程中,我們會譜出銀行網頁應用程式的基礎,使用 HTML 模板建立不同的畫面,各自顯示並更新內容,而不必每次都需要載入整個 HTML 頁面。
|
||||
|
||||
### 開始之前
|
||||
|
||||
你需要一個網頁伺服器來測試我們要建的專案。如果你還沒有,你可以安裝 [Node.js](https://nodejs.org) 並在你的專案資料夾中使用指令 `npx lite-server`。這會建立一個本地端的網頁伺服器,在瀏覽器上開啟你的網頁程式。
|
||||
|
||||
### 準備
|
||||
|
||||
在你的電腦上,建立資料夾 `bank`,並在裡面建立檔案 `index.html`。我們以這個 HTML [樣板](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)
|
||||
|
||||
如果你想在同一個網頁上建立不同的畫面,其中一種方法是各自建立一個 HTML 檔給每一個你想呈現的子畫面。然而,這個方式有許多不便之處:
|
||||
|
||||
- 你需要在切換頁面時,重新載入整個網頁。這會很花時間。
|
||||
- 在不同子頁面上共享數據會是一大難題。
|
||||
|
||||
另一個解決方案是只有一個 HTML 檔案,並使用 `<template>` 元素定義多個 [HTML 模板](https://developer.mozilla.org/docs/Web/HTML/Element/template)。
|
||||
一個模板提供可重複利用的 HTML 區塊,它不會顯示在瀏覽器上,而在需要之時由 JavaScript 以呈現出來。
|
||||
|
||||
### 課題
|
||||
|
||||
我們會建立一個銀行網頁應用程式,其中包含兩個子畫面:登入頁面與儀表板頁面。首先,我們在網頁應用程式的 HTML body 上,建立放置區來放置模板的子頁面。
|
||||
|
||||
```html
|
||||
<div id="app">Loading...</div>
|
||||
```
|
||||
|
||||
我們給它 `id`,以利後續 JavaScript 對它的追蹤。
|
||||
|
||||
> 提示:因為它裡面元素的內容會被置換,我們可以建立載入中訊息或提示,在應用程式載入時顯示出來。
|
||||
|
||||
接下來,我們加入下列的 HTML 模板,給登入畫面使用。現在我們只加入一行標題與一個有連結的區塊,進行簡單的功能。
|
||||
|
||||
```html
|
||||
<template id="login">
|
||||
<h1>Bank App</h1>
|
||||
<section>
|
||||
<a href="/dashboard">Login</a>
|
||||
</section>
|
||||
</template>
|
||||
```
|
||||
|
||||
接著,加入另一個 HTML 模板給儀表板頁面。這個頁面就會包含不同的區塊:
|
||||
|
||||
- 包含標題與登出連結的網頁標頭
|
||||
- 現在的銀行帳戶餘額
|
||||
- 一個歷史交易清單的表格
|
||||
|
||||
```html
|
||||
<template id="dashboard">
|
||||
<header>
|
||||
<h1>Bank App</h1>
|
||||
<a href="/login">Logout</a>
|
||||
</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>
|
||||
```
|
||||
|
||||
> 提示:在建立 HTML 模板時,如果你想知道它的呈現樣子為何,你可以註解掉 `<template>` 與 `</template>`。使用 `<!-- -->` 來註解它們。
|
||||
|
||||
✅ 你知道為什麼我們需要使用模板的 `id` 屬性嗎?我們可以使用別的屬性,例如 classes 嗎?
|
||||
|
||||
## 利用 JavaScript 顯示模板
|
||||
|
||||
現在,如果你使用瀏覽器打開你的應用程式,你會看到畫面卡在 `Loading...` 的畫面。那是因為我們需要為它新增一些 JavaScript 的程式碼來顯示出這些 HTML 模板。
|
||||
|
||||
展現模板通常需要三個步驟:
|
||||
|
||||
1. 在 DOM 內接收模板元素,舉例來說,使用 [`document.getElementById`](https://developer.mozilla.org/docs/Web/API/Document/getElementById)。
|
||||
2. 複製模板元素,使用 [`cloneNode`](https://developer.mozilla.org/docs/Web/API/Node/cloneNode)。
|
||||
3. 將複製元素接到 DOM 的顯示元素上,例如使用 [`appendChild`](https://developer.mozilla.org/docs/Web/API/Node/appendChild)。
|
||||
|
||||
✅ 我們為什麼需要在接到 DOM 前,複製一份模板?你能想像如果我們省略了此步驟後,會發生什麼事嗎?
|
||||
|
||||
### 課題
|
||||
|
||||
在資料夾中建立新檔案 `app.js`,並在你的 HTML 檔案的 `<head>` 區塊中中匯入這個新檔案:
|
||||
|
||||
```html
|
||||
<script src="app.js" defer></script>
|
||||
```
|
||||
|
||||
在 `app.js` 中,我們建立新函式 `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);
|
||||
}
|
||||
```
|
||||
|
||||
這裡做的事情就是我們上述提及過的三個步驟。我們使用 `templateId` 展現了模板,並將複製的內容接在我們的放置區中。注意我們需要使用 `cloneNode(true)` 來複製整個模板的子樹。
|
||||
|
||||
現在我們呼叫這個函式,指定特定的模板並觀察結果。
|
||||
|
||||
```js
|
||||
updateRoute('login');
|
||||
```
|
||||
|
||||
✅ 程式碼中 `app.innerHTML = '';` 的目的為何?如果刪去它會發生什麼事?
|
||||
|
||||
## 建立網頁路由(Routing)
|
||||
|
||||
當提及網頁應用程式時,我們稱呼 *路由(Routing)* 來連接**網址(URLs)**到特定的畫面上,呈現相關內容。一個含有多個 HTML 檔的網頁,網址又象徵著檔案路徑,這能自動地完成網址與檔案的轉換。舉例來說,專案資料夾內有這些檔案:
|
||||
|
||||
```
|
||||
mywebsite/index.html
|
||||
mywebsite/login.html
|
||||
mywebsite/admin/index.html
|
||||
```
|
||||
|
||||
若我們建立網路伺服器,根目錄為 `mywebsite`,則 URL 路由為:
|
||||
|
||||
```
|
||||
https://site.com --> mywebsite/index.html
|
||||
https://site.com/login.html --> mywebsite/login.html
|
||||
https://site.com/admin/ --> mywebsite/admin/index.html
|
||||
```
|
||||
|
||||
然而,在我們的網頁應用中,我們使用單一個 HTML 檔包含所有的子畫面到其中,所以預設的路由行為並不能幫助到本次專案。我們需要手動進行連接,使用 JavaScript 更新該被顯示出來的模板。
|
||||
|
||||
### 課題
|
||||
|
||||
我們使用簡單的物件來達成 URL 網址與模板的[關聯實體關係](https://en.wikipedia.org/wiki/Associative_array)。加入這個物件到 `app.js` 檔案的最上方。
|
||||
|
||||
```js
|
||||
const routes = {
|
||||
'/login': { templateId: 'login' },
|
||||
'/dashboard': { templateId: 'dashboard' },
|
||||
};
|
||||
```
|
||||
|
||||
現在,我們對函式 `updateRoute` 做一些更動。我們不直接將 `templateId` 作為參數傳遞,而是接收現在的 URL 網址,在使用關聯表來取得相對應的模板 ID 數值。我們可以使用 [`window.location.pathname`](https://developer.mozilla.org/docs/Web/API/Location/pathname) 來取得網址的部分路徑。
|
||||
|
||||
```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);
|
||||
}
|
||||
```
|
||||
|
||||
這邊我們建立了模板的路由關係。你可以藉由修改網址,來測試你的網頁是否正確的轉移。
|
||||
|
||||
✅ 如果你輸入了不存在的網址,它會發生什麼事?我們該如何解決呢?
|
||||
|
||||
## 加入網頁訪問
|
||||
|
||||
下一個步驟為在不更改網址的情況下,新增網頁訪問的途徑。這會做出兩件事情:
|
||||
|
||||
1. 更新現在的網址
|
||||
2. 更新要被顯示的模板到新的網址中
|
||||
|
||||
我們已經完成了第二點,藉由使用函式 `updateRoute` 來完成,所以我們需要釐清該如何更新現在的網址。
|
||||
|
||||
我們需要使用 JavaScript,詳細來看為 [`history.pushState`](https://developer.mozilla.org/docs/Web/API/History/pushState),更新網址位置並建立瀏覽紀錄,同時不更新整個 HTML 頁面。
|
||||
|
||||
> 筆記:網頁超連結元素 [`<a href>`](https://developer.mozilla.org/docs/Web/HTML/Element/a) 可以建立不同網址的連接,但它預設上會讓瀏覽器重新載入 HTML 檔。我們需要手動新增 JavaScript 處理路由以避免此行為發生,在點擊事件中使用函式 preventDefault() 。
|
||||
|
||||
### 課題
|
||||
|
||||
我們來建立新的函式,讓應用程式得以做網頁的訪問:
|
||||
|
||||
```js
|
||||
function navigate(path) {
|
||||
window.history.pushState({}, path, path);
|
||||
updateRoute();
|
||||
}
|
||||
```
|
||||
|
||||
這個方法根據導入的路徑位置,更新了現在的網址位置,再更新模板上去。`window.location.origin` 回傳了網址的根路徑,允許我們重新構築完整的網址。
|
||||
|
||||
現在,藉由上述的函式,我們可以解決找不到網頁路徑的問題。我們修改函式 `updateRoute`,在找不到該網頁時強制轉移到一個存在的網頁。
|
||||
|
||||
```js
|
||||
function updateRoute() {
|
||||
const path = window.location.pathname;
|
||||
const route = routes[path];
|
||||
|
||||
if (!route) {
|
||||
return navigate('/login');
|
||||
}
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
如果找不到網頁路由時,我們會導往 `login` 的頁面。
|
||||
|
||||
現在,我們建立新的函式,在連結被點擊時取得網址位置,並避免瀏覽器進行預設上的重新載入:
|
||||
|
||||
```js
|
||||
function onLinkClick(event) {
|
||||
event.preventDefault();
|
||||
navigate(event.target.href);
|
||||
}
|
||||
```
|
||||
|
||||
現在我們完成應用程式的網頁訪問系統,在 HTML 檔的 *Login* 與 *Logout* 連結加入此函式。
|
||||
|
||||
```html
|
||||
<a href="/dashboard" onclick="onLinkClick(event)">Login</a>
|
||||
...
|
||||
<a href="/login" onclick="onLinkClick(event)">Logout</a>
|
||||
```
|
||||
|
||||
使用 [`onclick`](https://developer.mozilla.org/docs/Web/API/GlobalEventHandlers/onclick) 屬性會將 `click` 事件連接到 JavaScript 程式碼中,這邊會再呼叫函式 `navigate()`。
|
||||
|
||||
試著點擊這些連結,你應該能造訪網頁中不同的的畫面了。
|
||||
|
||||
✅ `history.pushState` 這個方法是 HTML5 標準的一部份,支援在[所有當代的瀏覽器](https://caniuse.com/?search=pushState)上。如果你要為舊款的瀏覽器設計網頁應用程式的話,這邊有一個技巧來加在這個 API 上:在路徑前面加上 [hash (`#`)](https://en.wikipedia.org/wiki/URI_fragment),你可以完成網頁路由與不須重載網頁的功能,它的目的就是在同一個網頁中做內部連結的切換。
|
||||
|
||||
## 處理瀏覽器的「上一頁」與「下一頁」
|
||||
|
||||
使用 `history.pushState` 會建立瀏覽器的瀏覽紀錄。你可以使用瀏覽器的*上一頁*來確認,它應該要能呈現像這樣的畫面:
|
||||
|
||||

|
||||
|
||||
點擊上一頁數次,你會看到網址會改變且歷史紀錄也更新上去了,但同一個模板還是被顯示出來。
|
||||
|
||||
這是因為網頁不知道該如何依據歷史紀錄來呼叫 `updateRoute()`。如果你閱讀了 [`history.pushState` 技術文件](https://developer.mozilla.org/docs/Web/API/History/pushState),你會發現如果狀態改變 ── 同時代表著網址改變 ── [`popstate`](https://developer.mozilla.org/docs/Web/API/Window/popstate_event) 事件就會被觸發。我們會利用此特徵來修復這個問題。
|
||||
|
||||
### 課題
|
||||
|
||||
為了在瀏覽器歷史改變時更新該被顯示的模板,我們會以新函式來呼叫 `updateRoute()`。我們在 `app.js` 檔最下方加入:
|
||||
|
||||
```js
|
||||
window.onpopstate = () => updateRoute();
|
||||
updateRoute();
|
||||
```
|
||||
|
||||
> 筆記:我們在這裡使用[箭頭函式](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Functions/Arrow_functions),簡短地宣告 `popstate` 事件處理器。它與正規的函式的功能是一樣的。
|
||||
|
||||
這是關於箭頭函式的回想影片:
|
||||
|
||||
[](https://youtube.com/watch?v=OP6eEbOj2sc "箭頭函式")
|
||||
|
||||
> 點擊上方圖片以觀看關於箭頭函式的影片。
|
||||
|
||||
現在,試著點擊瀏覽器上的上一頁與下一頁,檢查這次模板是否正確地更新出來。
|
||||
|
||||
---
|
||||
|
||||
## 🚀 挑戰
|
||||
|
||||
加入新的模板與對應的關聯表,顯示出本應用程式第三頁的功能 ── 帳戶餘額。
|
||||
|
||||
## 課後測驗
|
||||
|
||||
[課後測驗](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/42?loc=zh_tw)
|
||||
|
||||
## 複習與自學
|
||||
|
||||
網頁路由是網頁開發中很棘手的部分,特別是將網頁切換轉變為單一頁面應用程式(Single Page Application)。閱讀關於[Azure Static Web App 提供服務的方式](https://docs.microsoft.com/azure/static-web-apps/routes/?WT.mc_id=academic-77807-sagibbon)以處理網頁路由。你能解釋為什麼文件上的某些決定會如此重要呢?
|
||||
|
||||
## 作業
|
||||
|
||||
[增進網頁路由](assignment.zh-tw.md)
|
@@ -1,14 +0,0 @@
|
||||
# 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.
|
@@ -1,15 +0,0 @@
|
||||
# Améliorer le routage
|
||||
|
||||
## Instructions
|
||||
|
||||
|
||||
La déclaration de routes ne contient actuellement que l'ID de modèle à utiliser. Mais lors de l'affichage d'une nouvelle page, un peu plus que celà est parfois nécessaire. Améliorons notre implémentation de routage avec deux fonctionnalités supplémentaires :
|
||||
|
||||
- Donnez des titres à chaque modèle et mettez à jour le titre de la fenêtre avec ce nouveau titre lorsque le modèle change.
|
||||
- Ajouter une option pour exécuter du code après les modifications du modèle. Nous voulons afficher « Le tableau de bord est affiché » dans la console du développeur à chaque fois que la page du tableau de bord est affichée.
|
||||
|
||||
## Rubrique
|
||||
|
||||
| Critères | Exemplaire | Adéquat | Besoin d'amélioration |
|
||||
| -------- | ---------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
|
||||
| | Les deux fonctionnalités sont implémentées et fonctionnent. L'ajout de titre et de code fonctionne également pour une nouvelle route ajoutée dans la déclaration `routes`. | Les deux fonctionnalités fonctionnent, mais le comportement est codé en dur et non configurable via la déclaration `routes`. L'ajout d'une troisième route avec une addition de titre et de code ne fonctionne pas ou fonctionne partiellement. | L'une des fonctionnalités est manquante ou ne fonctionne pas correctement. |
|
@@ -1,14 +0,0 @@
|
||||
# रूटिंग में सुधार करें
|
||||
|
||||
## अनुदेश
|
||||
|
||||
मार्गों की घोषणा में वर्तमान में उपयोग करने के लिए केवल टेम्पलेट आईडी है। लेकिन एक नया पृष्ठ प्रदर्शित करते समय, कभी-कभी थोड़ा और अधिक की आवश्यकता होती है। आइए दो अतिरिक्त सुविधाओं के साथ हमारे रूटिंग कार्यान्वयन में सुधार करें:
|
||||
|
||||
- प्रत्येक टेम्पलेट को शीर्षक दें और जब टेम्पलेट बदलता है तो इस नए शीर्षक के साथ विंडो शीर्षक को अपडेट करें।
|
||||
- टेम्पलेट परिवर्तन के बाद कुछ कोड चलाने के लिए एक विकल्प जोड़ें। हम हर बार डैशबोर्ड पृष्ठ प्रदर्शित होने पर डेवलपर कंसोल में `'Dashboard is shown'` प्रिंट करना चाहते हैं
|
||||
|
||||
## शीर्ष
|
||||
|
||||
| मानदंड | उदाहरणात्मक | पर्याप्त | सुधार की जरूरत |
|
||||
| ------ | ------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
|
||||
| | दो विशेषताएं कार्यान्वित और काम कर रही हैं। शीर्षक और कोड जोड़ भी `routes` घोषणा में जोड़े गए एक नए मार्ग के लिए काम करते हैं. | दो सुविधाएँ काम करती हैं, लेकिन व्यवहार कठिन है और `routes` घोषणा के माध्यम से विन्यास योग्य नहीं है। शीर्षक और कोड जोड़ के साथ तीसरा मार्ग जोड़ना आंशिक रूप से काम या काम नहीं करता है. | सुविधाओं में से एक गायब है या ठीक से काम नहीं कर रहा है. |
|
@@ -1,14 +0,0 @@
|
||||
# Migliorare l'instradamento
|
||||
|
||||
## Istruzioni
|
||||
|
||||
La dichiarazione delle rotte contiene attualmente solo l'ID del modello da utilizzare. Quando si visualizza una nuova pagina, a volte è necessario un po 'di più. L'implementazione del routing viene migliorata con due funzionalità aggiuntive:
|
||||
|
||||
- Assegnare titoli a ciascun modello e aggiornare il titolo della finestra con questo nuovo titolo quando il modello cambia.
|
||||
- Aggiungere un'opzione per eseguire del codice dopo le modifiche al modello. Si vuole stampare `"Il cruscotto è mostrato" nella` console per sviluppatori ogni volta che viene visualizzata la pagina del dashboard.
|
||||
|
||||
## Rubrica
|
||||
|
||||
| Criteri | Ottimo | Adeguato | Necessita miglioramento |
|
||||
| -------- | ---------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
|
||||
| | Le due funzionalità sono implementate e funzionanti. L'aggiunta di titolo e codice funziona anche per una nuova rotta aggiunta nella dichiarazione di `routes` . | Sono funzionanti i due comportamenti aggiuntivi, ma il comportamento è fisso nel codice e non è configurabile tramite la dichiarazione di `routes`. L'aggiunta di una terza rotta con l'aggiunta di titolo e codice non funziona o funziona parzialmente. | Una delle funzionalità manca o non funziona correttamente. |
|
@@ -1,14 +0,0 @@
|
||||
# ルーティングの改善
|
||||
|
||||
## 説明書
|
||||
|
||||
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.
|
||||
|
||||
## ルーブリック
|
||||
|
||||
| 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. |
|
@@ -1,14 +0,0 @@
|
||||
# 라우팅 개선
|
||||
|
||||
## 설명
|
||||
|
||||
경로 선언에는 현재 사용할 템플릿 ID만 포함됩니다. 하지만 새 페이지를 표시할 때는 추가할 것이 더 생기는 경우가 있습니다. 두 가지 추가 기능을 사용하여 라우팅 구현을 개선해 봅시다.
|
||||
|
||||
- 각 템플릿에 제목을 부여하고 템플릿이 변경되면 새 제목으로 창 제목을 변경합니다.
|
||||
- 템플릿 변경 후 일부 코드를 실행하는 옵션을 추가합니다. 대시보드 페이지가 표시될 때마다 개발자 콘솔에 `'대시보드가 보임'`을 출력합니다.
|
||||
|
||||
## 평가 기준
|
||||
|
||||
기준 | 모범 답안 | 적당한 답안 | 개선이 필요한 답안
|
||||
--- | --- | --- | ---
|
||||
| 두 기능이 구현되고 작동하는 경우. 제목 및 코드 추가는 `routes` 선언에 추가된 새 경로에서도 작동합니다. | 두 기능은 작동하지만, 동작은 하드 코딩되어 있으며 `routes` 선언을 통해 구성할 수 없는 경우. <br> 세 번째 경로를 제목과 코드와 함께 추가하면 작동하지 않거나 부분적으로 작동합니다. | 기능 중 하나가 없거나 제대로 작동하지 않는 경우
|
@@ -1,14 +0,0 @@
|
||||
# Tingkatkan penghalaan
|
||||
|
||||
## Arahan
|
||||
|
||||
Deklarasi laluan hanya mengandungi ID templat yang akan digunakan. Tetapi ketika memaparkan halaman baru, kadang-kadang diperlukan lebih banyak lagi. Mari tingkatkan pelaksanaan penghalaan kami dengan dua ciri tambahan:
|
||||
|
||||
- Berikan tajuk untuk setiap templat dan kemas kini tajuk tetingkap dengan tajuk baru ini apabila templat berubah.
|
||||
- Tambahkan pilihan untuk menjalankan beberapa kod setelah templat berubah. Kami ingin mencetak `"Papan Pemuka ditunjukkan"` di konsol pembangun setiap kali halaman papan pemuka dipaparkan.
|
||||
|
||||
## Rubrik
|
||||
|
||||
| Kriteria | Contoh | Mencukupi | Usaha Lagi |
|
||||
| -------- | ---------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
|
||||
| | Kedua-dua ciri tersebut dilaksanakan dan berfungsi. Penambahan tajuk dan kod juga berfungsi untuk laluan baru yang ditambahkan dalam deklarasi `route` | Kedua-dua ciri berfungsi, tetapi tingkah laku itu dikodkan keras dan tidak dapat dikonfigurasi melalui deklarasi `route`. Menambah laluan ketiga dengan penambahan tajuk dan kod tidak berfungsi atau berfungsi secara separa. | Salah satu ciri hilang atau tidak berfungsi dengan baik. |
|
@@ -1,14 +0,0 @@
|
||||
# Verbeter de routing
|
||||
|
||||
## Instructies
|
||||
|
||||
De routes-declaratie bevat momenteel alleen de te gebruiken sjabloon-ID. Maar bij het tonen van een nieuwe pagina is soms iets meer nodig. Laten we onze routeringsimplementatie verbeteren met twee extra functies:
|
||||
|
||||
- Geef elk sjabloon een titel en werk de venstertitel bij met deze nieuwe titel wanneer de sjabloon verandert.
|
||||
- Voeg een optie toe om wat code uit te voeren nadat de sjabloon is gewijzigd. Elke keer dat de dashboardpagina wordt weergegeven willen we `'Dashboard wordt weergegeven'` in de ontwikkelaarsconsole afdrukken.
|
||||
|
||||
## Rubriek
|
||||
|
||||
| Criteria | Voorbeeldig | Voldoende | Moet worden verbeterd |
|
||||
| -------- | ---------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
|
||||
| | De twee functies zijn geïmplementeerd en werken. Titel en codetoevoeging werken ook voor een nieuwe route die is toegevoegd in de `routes`-aangifte. | De twee functies werken, maar het gedrag is hard gecodeerd en kan niet worden geconfigureerd via de `routes`-aangifte. Het toevoegen van een derde route met toevoeging van titel en code werkt niet of gedeeltelijk. | Een van de functies ontbreekt of werkt niet goed. |
|
@@ -1,14 +0,0 @@
|
||||
# 增進網頁路由
|
||||
|
||||
## 簡介
|
||||
|
||||
我們的網頁路由的定義只包含模板的 ID。但當顯示新的網頁頁面時,我們或許會用到更多東西。讓我們來增進我們的網頁路由方式,新增兩項功能:
|
||||
|
||||
- 給各個模板標題,在模板切換後同時更新網頁視窗的標題。
|
||||
- 加入額外選項,在模板切換後執行特定程式。我們希望在切換到儀表板頁面時,在開發者命令欄顯示 `'Dashboard is shown'`。
|
||||
|
||||
## 學習評量
|
||||
|
||||
| 作業內容 | 優良 | 普通 | 待改進 |
|
||||
| -------- | -------------------------------------------------------- | --------------------------------------------------------------------------------------------- | ------------------------ |
|
||||
| | 兩個新功能運作正常,標題與程式也能執行新的 `routes` 規則 | 兩個新功能運作正常,但行為是 hardcoded 上去而非使用 `routes` 規則。新的路由規則無法完整地運作 | 新功能不完全或不正常運行 |
|
@@ -1,291 +0,0 @@
|
||||
# Cree un formulario de inicio de sesión y registro
|
||||
|
||||
## [Prueba previa a la conferencia](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/43)
|
||||
|
||||
### 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/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/docs/Web/HTML/Element/input) y [otros controles de formulario](https://developer.mozilla.org/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/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/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/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/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:
|
||||
|
||||

|
||||
|
||||
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/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/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/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`:
|
||||
|
||||
[](https://youtube.com/watch?v=4yJUTjtIlww "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/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.
|
||||
|
||||

|
||||
|
||||
✅ ¿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/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/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/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/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:
|
||||
|
||||

|
||||
|
||||
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:
|
||||
|
||||

|
||||
|
||||
## [Prueba posterior a la conferencia](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/44)
|
||||
|
||||
## 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.es.md)
|
@@ -1,301 +0,0 @@
|
||||
# Créer une application bancaire Partie 2: Créer un formulaire de connexion et d'inscription
|
||||
|
||||
## Quiz préalable
|
||||
|
||||
[Quiz préalable](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/43?loc=fr)
|
||||
|
||||
### Introduction
|
||||
|
||||
Dans presque toutes les applications Web modernes, vous pouvez créer un compte pour avoir votre propre espace privé. Comme plusieurs utilisateurs peuvent accéder à une application Web en même temps, vous avez besoin d'un mécanisme pour stocker les données personnelles de chaque utilisateur séparément et sélectionner les informations à afficher. Nous n'expliquerons pas comment gérer [l'identité de l'utilisateur en toute sécurité](https://en.wikipedia.org/wiki/Authentication) car il s'agit d'un sujet étendu en soi, mais nous veillerons à ce que chaque utilisateur puisse créer un (ou plusieurs) compte bancaire sur notre application.
|
||||
|
||||
Dans cette partie, nous utiliserons des formulaires HTML pour ajouter une connexion et une inscription à notre application Web. Nous verrons comment envoyer les données à une API serveur par programmation, et finalement comment définir des règles de validation de base pour les entrées utilisateur.
|
||||
|
||||
### Prérequis
|
||||
|
||||
Vous devez avoir terminé les [modèles HTML et routage](../../1-template-route/translations/README.fr.md) d'une application Web pour cette leçon. Vous devez également installer [Node.js](https://nodejs.org/fr) et [exécuter l'API du serveur](../../api/translations/README.fr.md) localement afin de pouvoir envoyer des données pour créer des comptes.
|
||||
|
||||
**Prenez note**
|
||||
Vous aurez deux terminaux fonctionnant en même temps comme indiqué ci-dessous.
|
||||
1. Pour l'application bancaire principale, nous avons intégré la leçon [Modèles HTML et routage](../../1-template-route/translations/README.fr.md)
|
||||
2. Pour l'[API du serveur Bank APP](../../api/translations/README.fr.md), que nous avons configuré ci-dessus.
|
||||
|
||||
Vous avez besoin que les deux serveurs soient opérationnels pour suivre le reste de la leçon. Ils écoutent sur différents ports (port `3000` et port `5000`) donc tout devrait bien fonctionner.
|
||||
|
||||
Vous pouvez tester que le serveur fonctionne correctement en exécutant cette commande dans un terminal :
|
||||
|
||||
```sh
|
||||
curl http://localhost:5000/api
|
||||
# -> devrait renvoyer "Bank API v1.0.0" comme résultat
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Formulaire et contrôles
|
||||
|
||||
L'élément `<form>` encapsule une section d'un document HTML où l'utilisateur peut saisir et soumettre des données avec des contrôles interactifs. Il existe toutes sortes de contrôles d'interface utilisateur (UI) qui peuvent être utilisés dans un formulaire, les plus courants étant les éléments `<input>` et `<button>`.
|
||||
|
||||
Il existe de nombreux [types](https://developer.mozilla.org/docs/Web/HTML/Element/input) différents de `<input>`, par exemple pour créer un champ où l'utilisateur peut saisir son nom d'utilisateur vous pouvez utiliser:
|
||||
|
||||
```html
|
||||
<input id="username" name="username" type="text">
|
||||
```
|
||||
|
||||
L'attribut `name` sera utilisé comme nom de propriété lorsque les données du formulaire seront envoyées. L'attribut `id` est utilisé pour associer un `<label>` au contrôle de formulaire.
|
||||
|
||||
> Jetez un œil à la liste complète des [types `<input>`](https://developer.mozilla.org/docs/Web/HTML/Element/input) et aux [autres contrôles de formulaire](https://developer.mozilla.org/docs/Learn/Forms/Other_form_controls) pour avoir une idée de tous les éléments d'interface utilisateur natifs que vous pouvez utiliser lors de la création de votre interface utilisateur.
|
||||
|
||||
✅ Notez que `<input>` est un [élément vide](https://developer.mozilla.org/docs/Glossary/Empty_element) sur lequel vous ne devez *pas* ajouter une balise de fermeture correspondante. Vous pouvez cependant utiliser la notation à fermeture automatique `<input/>`, mais ce n'est pas obligatoire.
|
||||
|
||||
L'élément `<button>` dans un formulaire est un peu spécial. Si vous ne spécifiez pas son attribut `type`, il soumettra automatiquement les données du formulaire au serveur lorsqu'il est pressé. Voici les valeurs de `type` possibles :
|
||||
|
||||
- `submit` : La valeur par défaut dans un `<form>`, le bouton déclenche l'action de soumission du formulaire.
|
||||
- `reset` : Le bouton réinitialise tous les contrôles du formulaire à leurs valeurs initiales.
|
||||
- `button` : n'attribue pas de comportement par défaut lorsque le bouton est enfoncé. Vous pouvez ensuite lui affecter des actions personnalisées à l'aide de JavaScript.
|
||||
|
||||
### Tâche
|
||||
|
||||
Commençons par ajouter un formulaire au modèle `login`. Nous aurons besoin d'un champ *nom d'utilisateur* et d'un bouton *Connexion*.
|
||||
|
||||
```html
|
||||
<template id="login">
|
||||
<h1>Bank App</h1>
|
||||
<section>
|
||||
<h2>Login</h2>
|
||||
<form id="loginForm">
|
||||
<label for="username">Username</label>
|
||||
<input id="username" name="user" type="text">
|
||||
<button>Login</button>
|
||||
</form>
|
||||
</section>
|
||||
</template>
|
||||
```
|
||||
|
||||
Si vous regardez de plus près, vous remarquerez que nous avons également ajouté un élément `<label>` ici. Les éléments `<label>` sont utilisés pour ajouter un nom aux contrôles de l'interface utilisateur, tels que notre champ de nom d'utilisateur. Les étiquettes sont importantes pour la lisibilité de vos formulaires, mais présentent également des avantages supplémentaires :
|
||||
|
||||
- En associant une étiquette à un contrôle de formulaire, il aide les utilisateurs utilisant des technologies d'assistance (comme un lecteur d'écran) à comprendre quelles données ils sont censés fournir.
|
||||
- Vous pouvez cliquer sur l'étiquette pour mettre directement l'accent sur l'entrée associée, ce qui facilite l'accès sur les appareils à écran tactile.
|
||||
|
||||
> [Accessibilité](https://developer.mozilla.org/docs/Learn/Accessibility/What_is_accessibility) sur le Web est un sujet très important qui est souvent négligé. Grâce aux [éléments HTML sémantiques](https://developer.mozilla.org/docs/Learn/Accessibility/HTML), il n'est pas difficile de créer du contenu accessible si vous les utilisez correctement. Vous pouvez [en savoir plus sur l'accessibilité](https://developer.mozilla.org/docs/Web/Accessibility) pour éviter les erreurs courantes et devenir un développeur responsable.
|
||||
|
||||
Nous allons maintenant ajouter un deuxième formulaire pour l'inscription, juste en dessous du précédent:
|
||||
|
||||
```html
|
||||
<hr/>
|
||||
<h2>Register</h2>
|
||||
<form id="registerForm">
|
||||
<label for="user">Username</label>
|
||||
<input id="user" name="user" type="text">
|
||||
<label for="currency">Currency</label>
|
||||
<input id="currency" name="currency" type="text" value="$">
|
||||
<label for="description">Description</label>
|
||||
<input id="description" name="description" type="text">
|
||||
<label for="balance">Current balance</label>
|
||||
<input id="balance" name="balance" type="number" value="0">
|
||||
<button>Register</button>
|
||||
</form>
|
||||
```
|
||||
|
||||
En utilisant l'attribut `value`, nous pouvons définir une valeur par défaut pour une entrée donnée.
|
||||
Notez également que l'entrée pour `balance` a le type `number`. Est-ce que ça a l'air différent des autres entrées ? Essayez d'interagir avec lui.
|
||||
|
||||
✅ Pouvez-vous naviguer et interagir avec les formulaires en utilisant uniquement un clavier ? Comment feriez-vous cela?
|
||||
|
||||
## Soumission des données au serveur
|
||||
|
||||
Maintenant que nous avons une interface utilisateur fonctionnelle, la prochaine étape consiste à envoyer les données à notre serveur. Faisons un test rapide avec notre code actuel : que se passe-t-il si vous cliquez sur le bouton *Connexion* ou *S'inscrire* ?
|
||||
|
||||
Avez-vous remarqué le changement dans la section URL de votre navigateur ?
|
||||
|
||||

|
||||
|
||||
L'action par défaut pour un `<form>` consiste à soumettre le formulaire à l'URL du serveur actuel à l'aide de la [méthode GET](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3 ), en ajoutant les données du formulaire directement à l'URL. Cette méthode a cependant quelques défauts :
|
||||
|
||||
- Les données envoyées sont de taille très limitée (environ 2000 caractères)
|
||||
- Les données sont directement visibles dans l'URL (pas génial pour les mots de passe)
|
||||
- Cela ne fonctionne pas avec les téléchargements de fichiers
|
||||
|
||||
C'est pourquoi vous pouvez le modifier pour utiliser la [méthode POST](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5) qui envoie les données du formulaire au serveur dans le corps de la requête HTTP, sans aucune des limitations précédentes.
|
||||
|
||||
> Alors que POST est la méthode la plus couramment utilisée pour envoyer des données, [dans certains scénarios spécifiques](https://www.w3.org/2001/tag/doc/whenToUseGet.html) il est préférable d'utiliser la méthode GET, lorsque l'on implémente un champ de recherche par exemple.
|
||||
|
||||
### Tâche
|
||||
|
||||
Ajoutez les propriétés `action` et `method` au formulaire d'inscription :
|
||||
|
||||
```html
|
||||
<form id="registerForm" action="//localhost:5000/api/accounts" method="POST">
|
||||
```
|
||||
|
||||
Essayez maintenant d'enregistrer un nouveau compte avec votre nom. Après avoir cliqué sur le bouton *S'inscrire*, vous devriez voir quelque chose comme ceci :
|
||||
|
||||

|
||||
|
||||
Si tout se passe bien, le serveur doit répondre à votre demande avec une réponse [JSON](https://www.json.org/json-fr.html) contenant les données de compte qui ont été créées.
|
||||
|
||||
✅ Essayez de vous enregistrer à nouveau avec le même nom. Que se produit'il?
|
||||
|
||||
## Soumettre des données sans recharger la page
|
||||
|
||||
Comme vous l'avez probablement remarqué, il y a un léger problème avec l'approche que nous venons d'utiliser : lors de la soumission du formulaire, nous sortons de notre application et le navigateur redirige vers l'URL du serveur. Nous essayons d'éviter tous les rechargements de pages avec notre application Web, car nous créons une [Application à page unique (SPA)](https://en.wikipedia.org/wiki/Single-page_application).
|
||||
|
||||
Pour envoyer les données du formulaire au serveur sans forcer le rechargement de la page, nous devons utiliser du code JavaScript. Au lieu de mettre une URL dans la propriété `action` d'un élément `<form>`, vous pouvez utiliser n'importe quel code JavaScript précédé de la chaîne `javascript:` pour effectuer une action personnalisée. L'utiliser signifie également que vous devrez implémenter certaines tâches qui étaient auparavant effectuées automatiquement par le navigateur :
|
||||
|
||||
- Récupérer les données du formulaire
|
||||
- Convertir et encoder les données du formulaire dans un format approprié
|
||||
- Créer la requête HTTP et l'envoyer au serveur
|
||||
|
||||
### Tâche
|
||||
|
||||
Remplacez le formulaire d'inscription `action` par :
|
||||
|
||||
```html
|
||||
<form id="registerForm" action="javascript:register()">
|
||||
```
|
||||
|
||||
Ouvrez `app.js` ajoutez une nouvelle fonction nommée `register` :
|
||||
|
||||
```js
|
||||
function register() {
|
||||
const registerForm = document.getElementById('registerForm');
|
||||
const formData = new FormData(registerForm);
|
||||
const data = Object.fromEntries(formData);
|
||||
const jsonData = JSON.stringify(data);
|
||||
}
|
||||
```
|
||||
|
||||
Ici, nous récupérons l'élément de formulaire à l'aide de `getElementById()` et utilisons l'assistant [`FormData`](https://developer.mozilla.org/docs/Web/API/FormData) pour extraire les valeurs des contrôles de formulaire en tant qu'ensemble de paires clé/valeur. Ensuite, nous convertissons les données en un objet normal à l'aide de [`Object.fromEntries()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/fromEntries) et enfin sérialisons les données en [JSON](https://www.json.org/json-fr.html), un format couramment utilisé pour échanger des données sur le Web.
|
||||
|
||||
Les données sont maintenant prêtes à être envoyées au serveur. Créez une nouvelle fonction nommée `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' };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
A quoi sert cette fonction ? Tout d'abord, notez le mot-clé `async` ici. Cela signifie que la fonction contient du code qui s'exécutera [**asynchrone**](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/async_function). Lorsqu'il est utilisé avec le mot-clé `await`, il permet d'attendre l'exécution du code asynchrone - comme attendre la réponse du serveur ici - avant de continuer.
|
||||
|
||||
Voici une vidéo rapide sur l'utilisation de `async/await` :
|
||||
|
||||
[](https://youtube.com/watch?v=YwmlRkrxvkk "Async et Await pour la gestion des promesses")
|
||||
|
||||
> 🎥 Cliquez sur l'image ci-dessus pour une vidéo sur async/await.
|
||||
|
||||
Nous utilisons l'API `fetch()` pour envoyer des données JSON au serveur. Cette méthode prend 2 paramètres :
|
||||
|
||||
- L'URL du serveur, donc on remet `//localhost:5000/api/accounts` à ce niveau.
|
||||
- Les paramètres de la requête. C'est là que nous définissons la méthode sur `POST` et fournissons le `body` de la requête. Comme nous envoyons des données JSON au serveur, nous devons également définir l'en-tête `Content-Type` sur `application/json` afin que le serveur sache comment interpréter le contenu.
|
||||
|
||||
Comme le serveur répondra à la demande avec JSON, nous pouvons utiliser `wait response.json()` pour analyser le contenu JSON et renvoyer l'objet résultant. Notez que cette méthode est asynchrone, nous utilisons donc le mot-clé `await` ici avant de revenir pour nous assurer que toutes les erreurs lors de l'analyse sont également détectées.
|
||||
|
||||
Ajoutez maintenant du code à la fonction `register` pour appeler `createAccount()`:
|
||||
|
||||
```js
|
||||
const result = await createAccount(jsonData);
|
||||
```
|
||||
|
||||
Comme nous utilisons ici le mot-clé `await`, nous devons ajouter le mot-clé `async` avant la fonction register:
|
||||
|
||||
```js
|
||||
async function register() {
|
||||
```
|
||||
|
||||
Enfin, ajoutons quelques logs pour vérifier le résultat. La fonction finale devrait ressembler à ceci:
|
||||
|
||||
```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 occurred:', result.error);
|
||||
}
|
||||
|
||||
console.log('Account created!', result);
|
||||
}
|
||||
```
|
||||
|
||||
C'était un peu long mais nous y sommes arrivés ! Si vous ouvrez vos [outils de développement de navigateur](https://developer.mozilla.org/docs/Learn/Common_questions/What_are_browser_developer_tools) et essayez d'enregistrer un nouveau compte, vous ne devriez voir aucun changement sur la page Web, mais un message s'affichera. apparaissent dans la console confirmant que tout fonctionne.
|
||||
|
||||

|
||||
|
||||
✅ Pensez-vous que les données sont envoyées au serveur en toute sécurité ? Et si quelqu'un était capable d'intercepter la demande ? Vous pouvez en savoir plus sur [HTTPS](https://fr.wikipedia.org/wiki/HTTPS) pour en savoir plus sur la communication de données sécurisée.
|
||||
|
||||
## La validation des données
|
||||
|
||||
Si vous essayez d'enregistrer un nouveau compte sans définir de nom d'utilisateur au préalable, vous pouvez voir que le serveur renvoie une erreur avec le code d'état [400 (Bad Request)](https://developer.mozilla.org/docs/Web/HTTP/Status/400#:~:text=The%20HyperText%20Transfer%20Protocol%20(HTTP,%2C%20or%20deceptive%20request%20routing).).
|
||||
|
||||
Avant d'envoyer des données à un serveur, il est recommandé de [valider les données du formulaire](https://developer.mozilla.org/docs/Learn/Forms/Form_validation) au préalable lorsque cela est possible, pour vous assurer d'envoyer une demande valide. Les contrôles de formulaires HTML5 fournissent une validation intégrée à l'aide de divers attributs :
|
||||
|
||||
- `required` : le champ doit être rempli sinon le formulaire ne peut pas être soumis.
|
||||
- `minlength` et `maxlength` : définit le nombre minimum et maximum de caractères dans les champs de texte.
|
||||
- `min` et `max` : définit la valeur minimum et maximum d'un champ numérique.
|
||||
- `type` : définit le type de données attendu, comme `numéro`, `e-mail`, `fichier` ou [autres types intégrés](https://developer.mozilla.org/docs/Web/HTML/Element/input). Cet attribut peut également modifier le rendu visuel du contrôle de formulaire.
|
||||
- `pattern` : permet de définir un pattern d'[expression régulière](https://developer.mozilla.org/docs/Web/JavaScript/Guide/Regular_Expressions) pour tester si les données saisies sont valides ou non.
|
||||
|
||||
> Astuce : vous pouvez personnaliser l'apparence de vos contrôles de formulaire selon qu'ils sont valides ou non en utilisant les pseudo-classes CSS `:valid` et `:invalid`.
|
||||
|
||||
### Tâche
|
||||
|
||||
Il y a 2 champs obligatoires pour créer un nouveau compte valide, le nom d'utilisateur et la devise, les autres champs étant facultatifs. Mettez à jour le code HTML du formulaire, en utilisant à la fois l'attribut `required` et le texte du libellé du champ:
|
||||
|
||||
```html
|
||||
<label for="user">Username (required)</label>
|
||||
<input id="user" name="user" type="text" required>
|
||||
...
|
||||
<label for="currency">Currency (required)</label>
|
||||
<input id="currency" name="currency" type="text" value="$" required>
|
||||
```
|
||||
|
||||
Bien que cette implémentation de serveur particulière n'impose pas de limites spécifiques sur la longueur maximale des champs, il est toujours recommandé de définir des limites raisonnables pour toute entrée de texte utilisateur.
|
||||
|
||||
Ajoutez un attribut `maxlength` aux champs de texte :
|
||||
|
||||
```html
|
||||
<input id="user" name="user" type="text" maxlength="20" required>
|
||||
...
|
||||
<input id="currency" name="currency" type="text" value="$" maxlength="5" required>
|
||||
...
|
||||
<input id="description" name="description" type="text" maxlength="100">
|
||||
```
|
||||
|
||||
Maintenant, si vous appuyez sur le bouton *S'inscrire* et qu'un champ ne respecte pas une règle de validation que nous avons définie, vous devriez voir quelque chose comme ceci :
|
||||
|
||||

|
||||
|
||||
Une validation comme celle-ci effectuée *avant* l'envoi de données au serveur est appelée validation **côté client**. Mais notez qu'il n'est pas toujours possible d'effectuer toutes les vérifications sans envoyer les données. Par exemple, nous ne pouvons pas vérifier ici si un compte existe déjà avec le même nom d'utilisateur sans envoyer de requête au serveur. Une validation supplémentaire effectuée sur le serveur est appelée validation **côté serveur**.
|
||||
|
||||
Généralement, les deux doivent être implémentés, et bien que l'utilisation de la validation côté client améliore l'expérience utilisateur en fournissant un retour instantané à l'utilisateur, la validation côté serveur est cruciale pour s'assurer que les données utilisateur que vous manipulez sont saines et sûres.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Challenge
|
||||
|
||||
Afficher un message d'erreur dans le code HTML si l'utilisateur existe déjà.
|
||||
|
||||
Voici un exemple de ce à quoi peut ressembler la page de connexion finale après un peu de style :
|
||||
|
||||

|
||||
|
||||
## Quiz de validation des connaissances
|
||||
|
||||
[Quiz de validation des connaissances](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/44?loc=fr)
|
||||
|
||||
## Révision et étude personnelle
|
||||
|
||||
Les développeurs sont devenus très créatifs dans leurs efforts de création de formulaires, en particulier en ce qui concerne les stratégies de validation. Découvrez les différents flux de formulaires en parcourant [CodePen](https://codepen.com); pouvez-vous trouver des formulaires intéressants et inspirants ?
|
||||
|
||||
## Affectation
|
||||
|
||||
[Concevez votre application bancaire](assignment.fr.md)
|
@@ -1,295 +0,0 @@
|
||||
# बैंकिंग ऐप पार्ट 2 बनाएँ: एक लॉगिन और पंजीकरण फॉर्म बनाएँ
|
||||
|
||||
## पूर्व व्याख्यान प्रश्नोत्तरी
|
||||
|
||||
[पूर्व व्याख्यान प्रश्नोत्तरी](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/43?loc=hi)
|
||||
|
||||
### परिचय
|
||||
|
||||
लगभग सभी आधुनिक वेब ऐप्स में, आप अपना निजी स्थान रखने के लिए एक खाता बना सकते हैं। चूंकि एक ही समय में कई उपयोगकर्ता वेब ऐप तक पहुंच सकते हैं, इसलिए आपको प्रत्येक उपयोगकर्ता के व्यक्तिगत डेटा को अलग से संग्रहीत करने के लिए एक तंत्र की आवश्यकता होती है और जानकारी प्रदर्शित करने के लिए कौन सी जानकारी का चयन करना चाहिए। हम [उपयोगकर्ता पहचान को सुरक्षित](https://en.wikipedia.org/wiki/Authentication) रूप से प्रबंधित करने के लिए को कवर नहीं करेंगे क्योंकि यह अपने आप में एक व्यापक विषय है, लेकिन हम सुनिश्चित करेंगे कि प्रत्येक उपयोगकर्ता एक बनाने में सक्षम है (या अधिक) हमारे ऐप पर बैंक खाता।
|
||||
|
||||
इस भाग में हम अपने वेब ऐप में लॉगिन और पंजीकरण को जोड़ने के लिए HTML रूपों का उपयोग करेंगे। हम देखेंगे कि डेटा को सर्वर एपीआई को प्रोग्रामेटिक रूप से कैसे भेजा जाए, और अंततः उपयोगकर्ता इनपुट के लिए बुनियादी सत्यापन नियमों को कैसे परिभाषित किया जाए।
|
||||
|
||||
### शर्त
|
||||
|
||||
इस पाठ के लिए आपको वेब ऐप का [HTML टेम्प्लेट और रूटिंग](../../1-template-route/translations/README.hi.md)) पूरा करना होगा। आपको स्थानीय रूप से [Node.js](https://nodejs.org) और [सर्वर एपीआई चलाने](../../api/README.hi.md) स्थापित करने की आवश्यकता है ताकि आप खाते बनाने के लिए डेटा भेज सकें।
|
||||
|
||||
आप परीक्षण कर सकते हैं कि सर्वर टर्मिनल में इस कमांड को निष्पादित करके ठीक से चल रहा है:
|
||||
|
||||
```sh
|
||||
curl http://localhost:5000/api
|
||||
# -> should return "Bank API v1.0.0" as a result
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## फोरम और कोन्टरोल्स
|
||||
|
||||
`<form>` एलेमेन्ट एक HTML दस्तावेज़ के एक भाग को एन्क्रिप्ट करता है जहां उपयोगकर्ता इनपुट कर सकता है और इंटरैक्टिव नियंत्रणों के साथ डेटा जमा कर सकता है। सभी प्रकार के उपयोगकर्ता इंटरफ़ेस (UI) नियंत्रण हैं जिनका उपयोग एक फॉर्म के भीतर किया जा सकता है, सबसे आम है `<input>` और `<button>` एलेमेन्ट ।
|
||||
|
||||
`<input>` के विभिन्न [प्रकार](https://developer.mozilla.org/docs/Web/HTML/Element/input) के कई उदाहरण हैं, उदाहरण के लिए एक फ़ील्ड बनाने के लिए जहां उपयोगकर्ता आप उपयोग कर सकते हैं इसके उपयोगकर्ता नाम दर्ज कर सकते हैं:
|
||||
|
||||
```html
|
||||
<input id="username" name="username" type="text">
|
||||
```
|
||||
|
||||
जब फॉर्म डेटा को भेज दिया जाएगा तो `name` विशेषता को संपत्ति के नाम के रूप में उपयोग किया जाएगा। `id` विशेषता का उपयोग फॉर्म नियंत्रण के साथ एक `label` को जोड़ने के लिए किया जाता है।
|
||||
|
||||
>
|
||||
एक विचार पाने के लिए [`<input>` प्रकार](https://developer.mozilla.org/docs/Web/HTML/Element/input) और [अन्य फोरम कोन्टरोल्स](https://developer.mozilla.org/docs/Learn/Forms/Other_form_controls) की संपूर्ण सूची पर एक नज़र डालें अपने UI का निर्माण करते समय आप उपयोग कर सकते हैं सभी देशी UI तत्व।
|
||||
|
||||
✅ ध्यान दें कि `<input>` एक [खाली एलेमेन्ट](https://developer.mozilla.org/docs/Glossary/Empty_element) है, जिस पर आपको एक मिलान समापन टैग नहीं जोड़ना चाहिए। हालाँकि आप स्व-समापन `<input/>` संकेतन का उपयोग कर सकते हैं, लेकिन इसकी आवश्यकता नहीं है।
|
||||
|
||||
फॉर्म के भीतर `<button>` एलेमेन्ट थोड़ा विशेष है। यदि आप इसकी `type` विशेषता निर्दिष्ट नहीं करते हैं, तो यह स्वचालित रूप से दबाए जाने पर सर्वर को फॉर्म डेटा प्रस्तुत करेगा। यहां संभावित `type` मान दिए गए हैं:
|
||||
|
||||
- `submit`: एक `form` के भीतर डिफ़ॉल्ट, बटन फार्म सबमिट एक्शन को ट्रिगर करता है।
|
||||
- `reset`: बटन उनके प्रारंभिक मूल्यों पर सभी फॉर्म नियंत्रणों को रीसेट करता है।
|
||||
- `button`: बटन दबाए जाने पर डिफ़ॉल्ट व्यवहार न निर्दिष्ट करें। फिर आप जावास्क्रिप्ट का उपयोग करके इसे कस्टम एक्शन दे सकते हैं।
|
||||
|
||||
### टास्क
|
||||
|
||||
आइए `login` टेम्प्लेट में एक फ़ॉर्म जोड़कर शुरू करें। हमें एक *उपयोगकर्ता नाम* फ़ील्ड और एक *लॉगिन* बटन की आवश्यकता होगी।
|
||||
|
||||
```html
|
||||
<template id="login">
|
||||
<h1>Bank App</h1>
|
||||
<section>
|
||||
<h2>Login</h2>
|
||||
<form id="loginForm">
|
||||
<label for="username">Username</label>
|
||||
<input id="username" name="user" type="text">
|
||||
<button>Login</button>
|
||||
</form>
|
||||
</section>
|
||||
</template>
|
||||
```
|
||||
|
||||
यदि आप एक करीब से देखते हैं, तो आप देख सकते हैं कि हमने यहां एक `<label>` एलेमेन्ट भी जोड़ा है। `<label>` एलेमेन्टस का उपयोग UI नियंत्रणों में एक नाम जोड़ने के लिए किया जाता है, जैसे कि हमारा उपयोगकर्ता नाम फ़ील्ड। लेबल आपके रूपों की पठनीयता के लिए महत्वपूर्ण हैं, लेकिन अतिरिक्त लाभ के साथ भी आते हैं:
|
||||
|
||||
- एक लेबल को फोरम कोन्टरोल्स से जोड़कर, यह उपयोगकर्ताओं को सहायक तकनीकों (जैसे कि स्क्रीन रीडर) का उपयोग करके यह समझने में मदद करता है कि उन्हें क्या डेटा प्रदान करने की उम्मीद है।
|
||||
- आप संबंधित इनपुट पर सीधे ध्यान केंद्रित करने के लिए लेबल पर क्लिक कर सकते हैं, जिससे टच-स्क्रीन आधारित उपकरणों तक पहुंचना आसान हो जाता है।
|
||||
|
||||
> वेब पर [एक्सेसिबिलिटी](https://developer.mozilla.org/docs/Learn/Accessibility/What_is_accessibility) एक बहुत ही महत्वपूर्ण विषय है जिसकी अक्सर अनदेखी की जाती है। [अर्थ संबंधी HTML एलेमेन्टस](https://developer.mozilla.org/docs/Learn/Accessibility/HTML) के लिए धन्यवाद यदि आप इन्हें ठीक से उपयोग करते हैं तो सुलभ सामग्री बनाना मुश्किल नहीं है। सामान्य गलतियों से बचने और एक जिम्मेदार डेवलपर बनने के लिए आप (पहुंच के बारे में और अधिक पढ़ सकते हैं](https://developer.mozilla.org/docs/Web/Accessibility)।
|
||||
|
||||
अब हम पंजीकरण के लिए दूसरा रूप जोड़ेंगे, पिछले एक के नीचे:
|
||||
|
||||
```html
|
||||
<hr/>
|
||||
<h2>Register</h2>
|
||||
<form id="registerForm">
|
||||
<label for="user">Username</label>
|
||||
<input id="user" name="user" type="text">
|
||||
<label for="currency">Currency</label>
|
||||
<input id="currency" name="currency" type="text" value="$">
|
||||
<label for="description">Description</label>
|
||||
<input id="description" name="description" type="text">
|
||||
<label for="balance">Current balance</label>
|
||||
<input id="balance" name="balance" type="number" value="0">
|
||||
<button>Register</button>
|
||||
</form>
|
||||
```
|
||||
|
||||
`value` विशेषता का उपयोग करके हम दिए गए इनपुट के लिए एक डिफ़ॉल्ट मान को परिभाषित कर सकते हैं।
|
||||
सूचना यह भी है कि `balance` के इनपुट में `number` प्रकार है। क्या यह अन्य इनपुटों की तुलना में अलग दिखता है? इसके साथ बातचीत करने का प्रयास करें।
|
||||
|
||||
✅ क्या आप केवल कीबोर्ड का उपयोग करके फ़ॉर्म के साथ नेविगेट और इंटरैक्ट कर सकते हैं? आप वह कैसे करेंगें?
|
||||
|
||||
## सर्वर पर डेटा जमा करना
|
||||
|
||||
अब जब हमारे पास एक कार्यात्मक UI है, तो अगला चरण हमारे सर्वर पर डेटा भेजने के लिए है। चलो हमारे वर्तमान कोड का उपयोग करके एक त्वरित परीक्षण करें: यदि आप *लॉगिन* या *रजिस्टर* बटन पर क्लिक करते हैं तो क्या होता है?
|
||||
|
||||
क्या आपने अपने ब्राउज़र के URL अनुभाग में परिवर्तन को देखा है?
|
||||
|
||||

|
||||
|
||||
`<form>` के लिए डिफ़ॉल्ट क्रिया [GET मेथड](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html##ecec9.3) का उपयोग करके वर्तमान सर्वर URL को फ़ॉर्म सबमिट करना है , फॉर्म डेटा को सीधे URL में जोड़ना। इस विधि में कुछ कमियाँ हैं:
|
||||
|
||||
- भेजा गया डेटा आकार में बहुत सीमित है (लगभग 2000 वर्ण)
|
||||
- डेटा सीधे URL में दिखाई देता है (पासवर्ड के लिए महान नहीं)
|
||||
- यह फ़ाइल अपलोड के साथ काम नहीं करता है
|
||||
|
||||
इसीलिए आप इसे [POST विधि](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5) का उपयोग करने के लिए बदल सकते हैं, जो शरीर में सर्वर को फॉर्म डेटा भेजता है HTTP अनुरोध, पिछली सीमाओं के बिना।
|
||||
|
||||
> जबकि POST डेटा को भेजने के लिए सबसे आम तौर पर उपयोग की जाने वाली विधि है, [कुछ विशिष्ट परिदृश्यों में](https://www.w3.org/2001/tag/doc/whenToUseGet.html) यह GET विधि का उपयोग करने के लिए बेहतर है, जब उदाहरण के लिए खोज क्षेत्र लागू करना।
|
||||
|
||||
### टास्क
|
||||
|
||||
पंजीकरण फॉर्म में `action` और `method` गुण जोड़ें:
|
||||
|
||||
```html
|
||||
<form id="registerForm" action="//localhost:5000/api/accounts" method="POST">
|
||||
```
|
||||
|
||||
अब अपने नाम के साथ एक नया खाता पंजीकृत करने का प्रयास करें। * रजिस्टर * बटन पर क्लिक करने के बाद आपको कुछ इस तरह से देखना चाहिए:
|
||||
|
||||

|
||||
|
||||
यदि सब कुछ ठीक हो जाता है, तो सर्वर को आपके अनुरोध का जवाब एक [JSON](https://www.json.org/json-en.html) प्रतिक्रिया के साथ देना चाहिए जिसमें खाता डेटा बनाया गया था।
|
||||
|
||||
✅ एक ही नाम के साथ फिर से पंजीकरण करने का प्रयास करें। क्या होता है?
|
||||
|
||||
## पृष्ठ को फिर से लोड किए बिना डेटा सबमिट करना
|
||||
|
||||
जैसा कि आपने शायद देखा है, हमारे द्वारा अभी उपयोग किए गए दृष्टिकोण के साथ एक मामूली समस्या है: फॉर्म जमा करते समय, हम अपने ऐप से बाहर निकलते हैं और ब्राउज़र सर्वर URL पर रीडायरेक्ट करता है। हम अपने वेब ऐप के साथ सभी पेज रीलोड से बचने की कोशिश कर रहे हैं, क्योंकि हम एक [सिंगल-पेज एप्लिकेशन (SPA)](https://en.wikipedia.org/wiki/Single-page_application) हैं।
|
||||
|
||||
पृष्ठ पुनः लोड किए बिना सर्वर को फ़ॉर्म डेटा भेजने के लिए, हमें जावास्क्रिप्ट कोड का उपयोग करना होगा। एक `<form>` तत्व की `action` प्रॉपर्टी में एक यूआरएल डालने के बजाय, आप कस्टम क्रिया करने के लिए `javascript` स्ट्रिंग द्वारा प्रचलित किसी भी जावास्क्रिप्ट कोड का उपयोग कर सकते हैं। इसका उपयोग करने का अर्थ यह भी है कि आपको कुछ कार्यों को लागू करना होगा जो पहले ब्राउज़र द्वारा स्वचालित रूप से किए गए थे:
|
||||
|
||||
- फॉर्म डेटा को पुनः प्राप्त करें
|
||||
- फ़ॉर्म डेटा को एक उपयुक्त प्रारूप में कनवर्ट और एन्कोड करें
|
||||
- HTTP रिक्वेस्ट बनाएं और इसे सर्वर पर भेजें
|
||||
|
||||
### टास्क
|
||||
|
||||
पंजीकरण फॉर्म को `action` से बदलें:
|
||||
|
||||
```html
|
||||
<form id="registerForm" action="javascript:register()">
|
||||
```
|
||||
|
||||
`app.js` खोलें `register` नामक एक नया फ़ंक्शन जोड़ें:
|
||||
|
||||
```js
|
||||
function register() {
|
||||
const registerForm = document.getElementById('registerForm');
|
||||
const formData = new FormData(registerForm);
|
||||
const data = Object.fromEntries(formData);
|
||||
const jsonData = JSON.stringify(data);
|
||||
}
|
||||
```
|
||||
|
||||
यहाँ हम `getElementById()` का उपयोग कर फॉर्म एलिमेंट को पुनः प्राप्त करते हैं और फॉर्म से मान निकालने के लिए [`FormData`](https://developer.mozilla.org/docs/Web/API/FormData/) का उपयोग करते हैं। की/वैल्यू जोड़े के एक सेट के रूप में नियंत्रण। फिर हम [`Object.fromEntries()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Object/Object/fromEntries) का उपयोग करके डेटा को एक नियमित ऑब्जेक्ट में परिवर्तित करते हैं और अंत में डेटा को [JSON](https://www.json.org/json-en.html) में क्रमबद्ध करते हैं, आमतौर पर वेब पर डेटा के आदान-प्रदान के लिए उपयोग किया जाने वाला प्रारूप।
|
||||
|
||||
डेटा अब सर्वर पर भेजे जाने के लिए तैयार है। `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' };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
यह क्या कार्य कर रहा है? सबसे पहले, यहां `async` कीवर्ड देखें। इसका मतलब है कि फ़ंक्शन में कोड शामिल है जो [**asynchronously**](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/async_function) को निष्पादित करेगा। जब `await` कीवर्ड का उपयोग किया जाता है, तो यह एसिंक्रोनस कोड को निष्पादित करने के लिए प्रतीक्षा करने की अनुमति देता है - जैसे सर्वर प्रतिक्रिया का इंतजार यहां जारी रखने से पहले।
|
||||
|
||||
यहाँ `async/await` उपयोग के बारे में एक त्वरित वीडियो है:
|
||||
|
||||
[](https://youtube.com/watch?v=YwmlRkrxvkk "परोमीसेस के प्रबंधन के लिए Async और Await")
|
||||
|
||||
> async/await के वीडियो के लिए ऊपर दी गई छवि पर क्लिक करें।
|
||||
|
||||
हम JSON डेटा को सर्वर पर भेजने के लिए `fetch()` API का उपयोग करते हैं। इस विधि में 2 पैरामीटर हैं:
|
||||
|
||||
- सर्वर का URL, इसलिए हमने यहां `//localhost:5000/api/accounts` वापस रखा है।
|
||||
- अनुरोध की सेटिंग। यही कारण है कि हम अनुरोध के लिए `POST` विधि निर्धारित करते हैं और `body` प्रदान करते हैं। जैसा कि हम JSON डेटा सर्वर पर भेज रहे हैं, हमें `Content-type` हेडर को `application/json` पर सेट करने की भी आवश्यकता है, इसलिए सर्वर को पता है कि सामग्री की व्याख्या कैसे करें।
|
||||
|
||||
जैसा कि सर्वर JSON के साथ अनुरोध का जवाब देगा, हम JSON सामग्री को पार्स करने के लिए `await response.json()` का उपयोग कर सकते हैं और परिणामी वस्तु वापस कर सकते हैं। ध्यान दें कि यह विधि अतुल्यकालिक है, इसलिए हम यह सुनिश्चित करने के लिए कि प्रतीक्षा के दौरान किसी भी त्रुटि को भी पकड़ा जाता है, लौटने से पहले हम यहाँ `await` कीवर्ड का उपयोग करते हैं।
|
||||
|
||||
अब `createAccount()` कहने के लिए `register` फ़ंक्शन में कुछ कोड जोड़ें:
|
||||
|
||||
```js
|
||||
const result = await createAccount(jsonData);
|
||||
```
|
||||
|
||||
चूँकि हम यहाँ `await` कीवर्ड का उपयोग करते हैं, हमें रजिस्टर फंक्शन से पहले `async` कीवर्ड जोड़ना होगा:
|
||||
|
||||
```js
|
||||
async function register() {
|
||||
```
|
||||
|
||||
अंत में, परिणाम को जांचने के लिए कुछ लॉग जोड़ें। अंतिम कार्य इस तरह दिखना चाहिए:
|
||||
|
||||
```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);
|
||||
}
|
||||
```
|
||||
|
||||
वह थोड़ा लंबा था लेकिन हम वहां पहुंच गए! यदि आप अपने [ब्राउज़र डेवलपर टूल] (https://developer.mozilla.org/docs/Learn/Common_questions/What_are_browser_developer_tools) खोलते हैं, और एक नया खाता आज़माते हैं, तो आपको वेब पेज पर कोई बदलाव नहीं देखना चाहिए लेकिन एक संदेश कंसोल में दिखाई देगा जो पुष्टि करता है कि सब कुछ काम करता है।
|
||||
|
||||

|
||||
|
||||
✅ क्या आपको लगता है कि डेटा सर्वर पर सुरक्षित रूप से भेजा जाता है? क्या होगा यदि कोई व्यक्ति अनुरोध को बाधित करने में सक्षम था? सुरक्षित डेटा संचार के बारे में अधिक जानने के लिए आप [HTTPS](https://en.wikipedia.org/wiki/HTTPS) के बारे में पढ़ सकते हैं।
|
||||
|
||||
## डेटा मान्य
|
||||
|
||||
यदि आप पहले उपयोगकर्ता नाम सेट किए बिना एक नया खाता पंजीकृत करने का प्रयास करते हैं, तो आप देख सकते हैं कि सर्वर स्थिति कोड [400 (खराब अनुरोध)](https://developer.mozilla.org/docs/Web/HTTP/Status/400#:~:text=The%20HyperText%20Transfer%20Protocol%20(HTTP,%2C%20or%20deceptive%20request%20routing).) के साथ एक त्रुटि देता है
|
||||
|
||||
किसी सर्वर पर डेटा भेजने से पहले, जब संभव हो, एक वैध अनुरोध भेजना सुनिश्चित करने के लिए पहले से [फॉर्म डेटा को मान्य करें](https://developer.mozilla.org/docs/Learn/Forms/Form_validation) यह एक अच्छा अभ्यास है। HTML5 फॉर्म नियंत्रण विभिन्न विशेषताओं का उपयोग करके अंतर्निहित मान्यता प्रदान करता है:
|
||||
|
||||
- `required`: फ़ील्ड को भरने की आवश्यकता है अन्यथा फॉर्म जमा नहीं किया जा सकता है।
|
||||
- `minlength` और `maxlength`: टेक्स्ट क्षेत्रों में न्यूनतम और अधिकतम वर्णों को परिभाषित करता है।
|
||||
- `min` और `max`:एक संख्यात्मक क्षेत्र के न्यूनतम और अधिकतम मूल्य को परिभाषित करता है।
|
||||
- `type`: अपेक्षित डेटा के प्रकार को परिभाषित करता है, जैसे `number`, `email`, `file` या [अन्य निर्मित प्रकार](https://developer.mozilla.org/docs/Web/HTML/Element/input). यह विशेषता फॉर्म नियंत्रण के दृश्य रेंडरिंग को भी बदल सकती है.
|
||||
- `pattern`: एक [रेगुलर इक्स्प्रेशन](https://developer.mozilla.org/docs/Web/JavaScript/Guide/Regular_Expressions) पैटर्न को निर्धारित करने के लिए परीक्षण करने की अनुमति देता है कि दर्ज किया गया डेटा वैध है या नहीं।
|
||||
|
||||
> युक्ति: यदि आप मान्य हैं और `:valid` और `:invalid` CSS छद्म-क्लेसे का उपयोग नहीं कर रहे हैं, तो आप अपने फ़ॉर्म नियंत्रणों को अनुकूलित कर सकते हैं।s.
|
||||
|
||||
### टास्क
|
||||
|
||||
वैध नया खाता बनाने के लिए 2 आवश्यक फ़ील्ड हैं, उपयोगकर्ता नाम और मुद्रा, अन्य फ़ील्ड वैकल्पिक हैं। फॉर्म के HTML को अपडेट करें, फ़ील्ड के लेबल में `required` विशेषता और टेक्स्ट दोनों का उपयोग करके:
|
||||
|
||||
```html
|
||||
<label for="user">Username (required)</label>
|
||||
<input id="user" name="user" type="text" required>
|
||||
...
|
||||
<label for="currency">Currency (required)</label>
|
||||
<input id="currency" name="currency" type="text" value="$" required>
|
||||
```
|
||||
|
||||
हालांकि यह विशेष सर्वर कार्यान्वयन अधिकतम लंबाई वाले क्षेत्रों पर विशिष्ट सीमाएँ लागू नहीं करता है, यह किसी भी उपयोगकर्ता पाठ प्रविष्टि के लिए उचित सीमा को परिभाषित करने के लिए हमेशा एक अच्छा अभ्यास है।
|
||||
|
||||
टेक्स्ट फ़ील्ड में एक `maxlength` विशेषता जोड़ें:
|
||||
|
||||
```html
|
||||
<input id="user" name="user" type="text" maxlength="20" required>
|
||||
...
|
||||
<input id="currency" name="currency" type="text" value="$" maxlength="5" required>
|
||||
...
|
||||
<input id="description" name="description" type="text" maxlength="100">
|
||||
```
|
||||
|
||||
अब यदि आप *रजिस्टर* बटन दबाते हैं और एक फ़ील्ड हमारे द्वारा परिभाषित सत्यापन नियम का सम्मान नहीं करता है, तो आपको कुछ इस तरह से देखना चाहिए:
|
||||
|
||||

|
||||
|
||||
इस तरह के सत्यापन *से पहले* किसी भी डेटा को सर्वर पर भेजने के लिए **क्लाइंट-साइड** सत्यापन कहा जाता है। लेकिन ध्यान दें कि डेटा भेजे बिना हमेशा सभी जांचों को बेहतर बनाना संभव नहीं है। उदाहरण के लिए, यदि सर्वर पर रिक्वेस्ट भेजे बिना एक ही यूज़रनेम के साथ कोई खाता पहले से मौजूद है तो हम यहाँ जाँच नहीं कर सकते। सर्वर पर निष्पादित अतिरिक्त सत्यापन को **सर्वर-साइड** सत्यापन कहा जाता है।
|
||||
|
||||
आमतौर पर दोनों को लागू करने की आवश्यकता होती है, और क्लाइंट-साइड सत्यापन का उपयोग करते समय उपयोगकर्ता को त्वरित प्रतिक्रिया प्रदान करके उपयोगकर्ता के अनुभव को बेहतर बनाता है, यह सुनिश्चित करने के लिए सर्वर-साइड सत्यापन महत्वपूर्ण है कि जिस उपयोगकर्ता डेटा में आप हेरफेर करते हैं वह ध्वनि और सुरक्षित है।
|
||||
|
||||
---
|
||||
|
||||
## 🚀 चुनौती
|
||||
|
||||
यदि उपयोगकर्ता पहले से मौजूद है, तो HTML में एक त्रुटि संदेश दिखाएं।
|
||||
|
||||
यहाँ एक उदाहरण दिया गया है कि अंतिम लॉगिन पृष्ठ स्टाइल के थोड़े समय बाद कैसा दिख सकता है:
|
||||
|
||||

|
||||
|
||||
## व्याख्यान उपरांत प्रश्नोत्तरी
|
||||
|
||||
[व्याख्यान उपरांत प्रश्नोत्तरी](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/44?loc=hi)
|
||||
|
||||
## समीक्षा और स्व अध्ययन
|
||||
|
||||
डेवलपर्स ने अपने फॉर्म निर्माण प्रयासों के बारे में, विशेष रूप से सत्यापन रणनीतियों के बारे में बहुत रचनात्मक जानकारी प्राप्त की है। [CodePen](https://codepen.com) के माध्यम से देख कर विभिन्न प्रकार के प्रवाह के बारे में जानें; क्या आप कुछ दिलचस्प और प्रेरक रूप पा सकते हैं?
|
||||
|
||||
## असाइनमेंट
|
||||
|
||||
[अपने बैंक ऐप को स्टाइल करें](assignment.hi.md)
|
@@ -1,293 +0,0 @@
|
||||
# Creazione di un'App Bancaria Parte 2: Creazione di un form di accesso e registrazione
|
||||
|
||||
## Quiz Pre-Lezione
|
||||
|
||||
[Quiz Pre-Lezione](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/43?loc=it)
|
||||
|
||||
### Introduzione
|
||||
|
||||
In quasi tutte le moderne app web, si può creare un account per avere il proprio spazio privato. Poiché più utenti possono accedere a un'app web contemporaneamente, è necessario un meccanismo per archiviare i dati personali di ciascun utente separatamente e selezionare le informazioni da visualizzare. Non verrà trattato come gestire [l'identità dell'utente in modo sicuro](https://it.wikipedia.org/wiki/Autenticazione) poiché si tratta di un argomento ampio di per sé, ma ci si assicurerà che ogni utente sia in grado di creare uno (o più) conto bancario nella app.
|
||||
|
||||
In questa parte si utilizzeranno form HTML per aggiungere login e registrazione all'app. Si vedrà come inviare i dati a un'API del server a livello di programmazione e, infine, come definire le regole di convalida di base per gli input dell'utente.
|
||||
|
||||
### Prerequisito
|
||||
|
||||
È necessario aver completato i [modelli HTML e il routing](../../1-template-route/translations/README.it.md) dell'app web per questa lezione. È inoltre necessario installare [Node.js](https://nodejs.org) ed [eseguirel'API del server](../../api/README.md) in locale in modo da poter inviare dati per creare account.
|
||||
|
||||
Si può verificare che il server funzioni correttamente eseguendo questo comando in un terminale:
|
||||
|
||||
```sh
|
||||
curl http://localhost:5000/api
|
||||
# -> dovrebbe restituire "Bank API v1.0.0" come risultato
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Form e controlli
|
||||
|
||||
L'elemento `<form>` incapsula una sezione di un documento HTML in cui l'utente può inserire e inviare dati con controlli interattivi. Esistono tutti i tipi di controlli dell'interfaccia utente (UI) che possono essere utilizzati all'interno di un form, i più comuni sono gli elementi `<input>` e `<button>`.
|
||||
|
||||
Esistono molti [tipi](https://developer.mozilla.org/docs/Web/HTML/Element/input) diversi di `<input>`, ad esempio per creare un campo in cui l'utente può inserire il proprio nome utente si può utilizzare:
|
||||
|
||||
```html
|
||||
<input id="username" name="username" type="text">
|
||||
```
|
||||
|
||||
L'attributo `name` verrà utilizzato come nome della proprietà quando verranno inviati i dati del form. L'attributo `id` viene utilizzato per associare un'etichetta (`<label>`) al relativo controllo nel form.
|
||||
|
||||
> Si dia un'occhiata all'intero elenco di [tipi di `<input>`](https://developer.mozilla.org/docs/Web/HTML/Element/input) e [altri controlli del form](https://developer.mozilla.org/docs/Learn/Forms/Other_form_controls) per avere un'idea di tutti gli elementi nativi dell'interfaccia utente che si possono utilizzare durante la creazione della propria interfaccia utente.
|
||||
|
||||
✅ Si noti che `<input>` è un [elemento vuoto](https://developer.mozilla.org/docs/Glossary/Empty_element) su cui *non* si dovrebbe aggiungere un tag di chiusura corrispondente. È comunque possibile utilizzare la notazione a chiusura automatica `<input/>` , ma non è richiesta.
|
||||
|
||||
L'elemento `<button>` all'interno di un form è un po' speciale. Se non si specifica il suo attributo di tipo (`type`) , invierà automaticamente i dati del form al server quando viene premuto. Ecco i possibili valori di `type` :
|
||||
|
||||
- `submit`: l'impostazione predefinita all'interno di un `<form>`, il pulsante attiva l'azione di invio del form.
|
||||
- `reset`: il pulsante ripristina tutti i controlli del form ai valori iniziali.
|
||||
- `button`: non assegna un comportamento predefinito quando viene premuto il pulsante. È quindi possibile assegnargli azioni personalizzate utilizzando JavaScript.
|
||||
|
||||
### Attività
|
||||
|
||||
Si comincia aggiungendo un form al modello di accesso `login` . Servirà di un campo *username* per il nome utente e di un pulsante *Login* .
|
||||
|
||||
```html
|
||||
<template id="login">
|
||||
<h1>Bank App</h1>
|
||||
<section>
|
||||
<h2>Login</h2>
|
||||
<form id="loginForm">
|
||||
<label for="username">Username</label>
|
||||
<input id="username" name="user" type="text">
|
||||
<button>Login</button>
|
||||
</form>
|
||||
</section>
|
||||
</template>
|
||||
```
|
||||
|
||||
Se si guarda più attentamente, si può notare che è stato aggiunto anche un elemento `<label>` (etichetta). Gli elementi `<label>` vengono utilizzati per aggiungere un nome ai controlli dell'interfaccia utente, come il campo username. Le etichette sono importanti per la leggibilità dei form, ma offrono anche vantaggi aggiuntivi:
|
||||
|
||||
- Associaer un'etichetta a un controllo in un form, aiuta gli utenti che utilizzano tecnologie assistite (come unlettore di schremo) a capire quali dati dovrebbero fornire.
|
||||
- È possibile fare clic sull'etichetta per portare direttamente il focus sull'input associato, rendendolo più facile da raggiungere su dispositivi basati su touch screen.
|
||||
|
||||
> [L'accessibilità](https://developer.mozilla.org/docs/Learn/Accessibility/What_is_accessibility) sul Web è un argomento molto importante che spesso viene trascurato. Grazie agli [elementi HTML semantici](https://developer.mozilla.org/docs/Learn/Accessibility/HTML) non è difficile creare contenuti accessibili se usati correttamente. Si può [leggere di più sull'accessibilità](https://developer.mozilla.org/docs/Web/Accessibility) per evitare errori comuni e diventare uno sviluppatore responsabile.
|
||||
|
||||
Ora si aggiungerà un secondo modulo per la registrazione, appena sotto il precedente:
|
||||
|
||||
```html
|
||||
<hr/>
|
||||
<h2>Register</h2>
|
||||
<form id="registerForm">
|
||||
<label for="user">Username</label>
|
||||
<input id="user" name="user" type="text">
|
||||
<label for="currency">Currency</label>
|
||||
<input id="currency" name="currency" type="text" value="$">
|
||||
<label for="description">Description</label>
|
||||
<input id="description" name="description" type="text">
|
||||
<label for="balance">Current balance</label>
|
||||
<input id="balance" name="balance" type="number" value="0">
|
||||
<button>Register</button>
|
||||
</form>
|
||||
```
|
||||
|
||||
Utilizzando l'attributo `value` si può impostare un valore predefinito per un dato input. Si noti inoltre che l'input per il saldo(`balance`) ha il tipo `number` (numero). Sembra diverso dagli altri input? Provare a interagire con esso.
|
||||
|
||||
✅ Si può navigare e interagire con i form utilizzando solo una tastiera? E come si pensa di fare?
|
||||
|
||||
## Invio dei dati al server
|
||||
|
||||
Ora che si ha un'interfaccia utente funzionale, il passaggio successivo è inviare i dati al server. Fare un rapido test utilizzando il codice attuale: cosa succede se si fa clic sul pulsante *Login* o *Register* ?
|
||||
|
||||
Si è notato il cambiamento nella sezione URL del proprio browser?
|
||||
|
||||

|
||||
|
||||
L'azione predefinita per un `<form>` consiste nell'inviare il form all'URL del server corrente utilizzando il [metodo GET](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3), aggiungendo i dati del modulo direttamente all'URL. Questo metodo presenta però alcuni difetti:
|
||||
|
||||
- I dati inviati sono di dimensioni molto limitate (circa 2000 caratteri)
|
||||
- I dati sono direttamente visibili nell'URL (non eccezionale per le password)
|
||||
- Non funziona con i caricamenti di file
|
||||
|
||||
Ecco perché si può cambiare e utilizzare il [metodo POST](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5) che invia i dati del modulo al server nel corpo della richiesta HTTP, senza nessuna delle limitazioni precedenti.
|
||||
|
||||
> Sebbene POST sia il metodo più comunemente utilizzato per inviare i dati, [in alcuni scenari specifici](https://www.w3.org/2001/tag/doc/whenToUseGet.html) è preferibile utilizzare il metodo GET, ad esempio quando si implementa un campo di ricerca.
|
||||
|
||||
### Attività
|
||||
|
||||
Aggiungere le proprietà `action` e `method` al form di registrazione:
|
||||
|
||||
```html
|
||||
<form id="registerForm" action="//localhost:5000/api/accounts" method="POST">
|
||||
```
|
||||
|
||||
Provare ora a registrare un nuovo account con il proprio nome. Dopo aver fatto clic sul pulsante *Register* si dovrebbe vedere qualcosa del genere:
|
||||
|
||||

|
||||
|
||||
Se tutto va bene, il server dovrebbe rispondere alla richiesta con una risposta [JSON](https://www.json.org/json-en.html) contenente i dati dell'account che è stato creato.
|
||||
|
||||
✅ Provare una nuova registrazione con lo stesso nome. Che cosa accade?
|
||||
|
||||
## Invio di dati senza ricaricare la pagina
|
||||
|
||||
Come probabilmente si è notato, c'è un piccolo problema con l'approccio appena usato: quando si invia il modulo, si esce dalla propria app e il browser reindirizza all'URL del server. Si sta cercando qui di evitare tutti i ricaricamenti delle pagine con quest app web, poiché si sta creando un'[applicazione a pagina singola (SPA)](https://en.wikipedia.org/wiki/Single-page_application).
|
||||
|
||||
Per inviare i dati del modulo al server senza forzare il ricaricamento di una pagina, si deve utilizzare il codice JavaScript. Invece di inserire un URL nella proprietà `action` di un `<form>`, si può utilizzare qualsiasi codice JavaScript anteposto dalla stringa `javascript:` per eseguire un'azione personalizzata. Usarlo significa anche che si dovranno implementare alcune attività che erano state precedentemente eseguite automaticamente dal browser:
|
||||
|
||||
- Recuperare i dati del form
|
||||
- Convertire e codificare i dati del form in un formato adatto
|
||||
- Creare la richiesta HTTP e inviarla al server
|
||||
|
||||
### Attività
|
||||
|
||||
Sostituire `action` nel form di registrazione con:
|
||||
|
||||
```html
|
||||
<form id="registerForm" action="javascript:register()">
|
||||
```
|
||||
|
||||
Aprire `app.js` aggiungere una nuova funzione denominata `register`:
|
||||
|
||||
```js
|
||||
function register() {
|
||||
const registerForm = document.getElementById('registerForm');
|
||||
const formData = new FormData(registerForm);
|
||||
const data = Object.fromEntries(formData);
|
||||
const jsonData = JSON.stringify(data);
|
||||
}
|
||||
```
|
||||
|
||||
Qui si recupera l'elemento form utilizzando `getElementById()` e si utilizza il [metodo di supporto FormData](https://developer.mozilla.org/docs/Web/API/FormData) per estrarre i valori dai controlli del forma come un insieme di coppie chiave/valore. Quindi si convertono i dati in un oggetto normale utilizzando [`Object.fromEntries()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/fromEntries) e infine si serializzano i dati in [JSON](https://www.json.org/json-en.html), un formato comunemente utilizzato per lo scambio di dati sul web.
|
||||
|
||||
I dati sono ora pronti per essere inviati al server. Creare una nuova funzione denominata `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' };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Cosa fa questa funzione? Per prima cosa notare la parola chiave `async`. Ciò significa che la funzione contiene codice che verrà eseguito [**in modo asincrono**](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/async_function). Quando viene utilizzato insieme alla parola chiave `await`, consente di attendere l'esecuzione del codice asincrono, come l'attesa della risposta del server qui, prima di continuare.
|
||||
|
||||
Ecco un breve video sull'utilizzo di `async/await`:
|
||||
|
||||
[](https://youtube.com/watch?v=YwmlRkrxvkk " gestione di promesse")
|
||||
|
||||
> Fare clic sull'immagine sopra per un video su async/await.
|
||||
|
||||
Si usa l'API `fetch()` per inviare dati JSON al server. Questo metodo richiede 2 parametri:
|
||||
|
||||
- L'URL del server, quindi rimettere `//localhost:5000/api/accounts` qui.
|
||||
- Le impostazioni della richiesta. È qui che si imposta il metodo su `POST` e si fornisce il corpo (`body`) della richiesta. Dato che si inviano dati JSON al server, si deve anche impostare l'intestazione `Content-Type` su `application/json` in modo che il server sappia come interpretare il contenuto.
|
||||
|
||||
Poiché il server risponderà alla richiesta con JSON, si può utilizzare `await response.json()` per analizzare il contenuto JSON e restituire l'oggetto risultante. Notare che questo metodo è asincrono, quindi si usa la parola chiave `await` qui prima di tornare per assicurarsi che vengano rilevati anche eventuali errori durante l'analisi.
|
||||
|
||||
Ora aggiungere del codice alla funzione di `register` per chiamare `createAccount()`:
|
||||
|
||||
```js
|
||||
const result = await createAccount(jsonData);
|
||||
```
|
||||
|
||||
Poiché qui si usa la parola chiave `await`, si deve aggiungere la parola chiave `async` prima della funzione di registrazione
|
||||
|
||||
```js
|
||||
async function register() {
|
||||
```
|
||||
|
||||
Infine, aggiungere alcuni log per verificare il risultato. La funzione finale dovrebbe essere simile a questa:
|
||||
|
||||
```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);
|
||||
}
|
||||
```
|
||||
|
||||
È stato un po' lungo ma si è arrivati! Se si apre [strumenti di sviluppo del browser](https://developer.mozilla.org/docs/Learn/Common_questions/What_are_browser_developer_tools) e si prava a registrare un nuovo account, non si dovrebbe vedere alcun cambiamento nella pagina web ma apparirà un messaggio nella console che conferma che tutto funziona.
|
||||
|
||||

|
||||
|
||||
✅ Si pensa che i dati vengano inviati al server in modo sicuro? E se qualcuno fosse in grado di intercettare la richiesta? Si possono leggere informazioni su [HTTPS](https://en.wikipedia.org/wiki/HTTPS) per saperne di più sulla comunicazione sicura dei dati.
|
||||
|
||||
## Convalida dati
|
||||
|
||||
Se si prova a registrare un nuovo account senza prima impostare un nome utente, si può vedere che il server restituisce un errore con codice di stato [400 Bad Request](https://developer.mozilla.org/docs/Web/HTTP/Status/400#:~:text=The%20HyperText%20Transfer%20Protocol%20(HTTP,%2C%20or%20deceptive%20request%20routing).), (richiesta non valida)
|
||||
|
||||
Prima di inviare i dati a un server è buona norma [convalidare i dati del modulo](https://developer.mozilla.org/docs/Learn/Forms/Form_validation) in anticipo quando possibile, per assicurarsi di inviare una richiesta valida. I controlli dei form HTML5 forniscono la convalida incorporata utilizzando vari attributi:
|
||||
|
||||
- `requested`: il campo deve essere compilato altrimenti il modulo non può essere inviato.
|
||||
- `minlength` e `maxlength`: definisce il numero minimo e massimo di caratteri nei campi di testo.
|
||||
- `min` e `max`: definisce il valore minimo e massimo di un campo numerico.
|
||||
- `type`: definisce il tipo di dati attesi, come `number`, `email`, `file` o [altri tipi incorporati](https://developer.mozilla.org/docs/Web/HTML/Element/input). Questo attributo può anche modificare il rendering visivo del form.
|
||||
- `pattern`: permette di definire un modello di [espressione regolare](https://developer.mozilla.org/docs/Web/JavaScript/Guide/Regular_Expressions) per verificare se i dati inseriti sono validi o meno.
|
||||
|
||||
> Suggerimento: si può personalizzare l'aspetto dei controlli del form a seconda che siano validi o meno utilizzando le pseudo-classi CSS `:valid` e `:invalid` .
|
||||
|
||||
### Attività
|
||||
|
||||
Ci sono 2 campi obbligatori per creare un nuovo account valido, nome utente (username) e la valuta (currency), gli altri campi sono opzionali. Aggiornare l'HTML del form, utilizzando sia l'attributo `required` che il testo nell'etichetta del campo in questo modo:
|
||||
|
||||
```html
|
||||
<label for="user">Username (required)</label>
|
||||
<input id="user" name="user" type="text" required>
|
||||
...
|
||||
<label for="currency">Currency (required)</label>
|
||||
<input id="currency" name="currency" type="text" value="$" required>
|
||||
```
|
||||
|
||||
Sebbene questa particolare implementazione del server non imponga limiti specifici sulla lunghezza massima dei campi, è sempre buona norma definire limiti ragionevoli per qualsiasi voce di testo inserita dell'utente.
|
||||
|
||||
Aggiungere un attributo `maxlength` ai campi di testo:
|
||||
|
||||
```html
|
||||
<input id="user" name="user" type="text" maxlength="20" required>
|
||||
...
|
||||
<input id="currency" name="currency" type="text" value="$" maxlength="5" required>
|
||||
...
|
||||
<input id="description" name="description" type="text" maxlength="100">
|
||||
```
|
||||
|
||||
Ora se si preme il pulsante *Register* e un campo non rispetta una regola di convalida che è stata definita, si dovrebbe vedere qualcosa del genere:
|
||||
|
||||

|
||||
|
||||
La convalida come questa eseguita *prima* di inviare qualsiasi dato al server è chiamata convalida **lato client** . Tenere presente che non è sempre possibile eseguire tutti i controlli senza inviare i dati. Ad esempio, non si può verificare qui se esiste già un account con lo stesso nome utente senza inviare una richiesta al server. La convalida aggiuntiva eseguita sul server è denominata convalida **lato server** .
|
||||
|
||||
Di solito devono essere implementate entrambe; mentre l'utilizzo della convalida lato client migliora l'esperienza dell'utente fornendo un feedback immediato all'utente, la convalida lato server è fondamentale per assicurarsi che i dati utente da manipolare siano validi e sicuri.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Sfida
|
||||
|
||||
Mostrare un messaggio di errore nell'HTML se l'utente esiste già.
|
||||
|
||||
Ecco un esempio di come può apparire la pagina di accesso finale dopo l'applicazione di un po' di stile:
|
||||
|
||||

|
||||
|
||||
## Quiz Post-Lezione
|
||||
|
||||
[Quiz post-lezione](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/44?loc=it)
|
||||
|
||||
## Revisione e Auto Apprendimento
|
||||
|
||||
Gli sviluppatori sono diventati molto creativi nei loro sforzi di costruzione di form, in particolare per quanto riguarda le strategie di convalida. Scoprire i diversi flussi di form cercando su [CodePen](https://codepen.com); si riescono a trovare dei form interessanti e stimolanti?
|
||||
|
||||
## Compito
|
||||
|
||||
[Dare uno stile alla propria app bancaria](assignment.it.md)
|
@@ -1,292 +0,0 @@
|
||||
# バンキングアプリを作ろう その 2: ログインと登録フォームの構築
|
||||
|
||||
## レッスン前の小テスト
|
||||
|
||||
[レッスン前の小テスト](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/43?loc=ja)
|
||||
|
||||
### イントロダクション
|
||||
|
||||
最近のほとんどの Web アプリでは、アカウントを作成して自分だけのプライベート空間を持つことができます。複数のユーザーが同時に Web アプリにアクセスすることができるため、各ユーザーの個人情報を個別に保存し、どの情報を表示するかを選択する仕組みが必要になります。ここでは、[ユーザー ID を安全に管理する方法](https://en.wikipedia.org/wiki/Authentication)については、それ自体が広範なトピックなので取り上げませんが、各ユーザーがアプリ上で1つ(または複数)の銀行口座を作成できるようにしておきます。
|
||||
|
||||
このパートでは、HTML フォームを使用して、Web アプリにログインと登録を追加します。プログラムでサーバー API にデータを送信する方法、最終的にユーザー入力の基本的な検証ルールを定義する方法を見ていきます。
|
||||
|
||||
### 前提条件
|
||||
|
||||
このレッスンでは、Web アプリの [HTML テンプレートとルーティング](../../1-template-route/translations/README.ja.md)が完了している必要があります。また、アカウントを作成するためのデータを送信できるように、ローカルに [Node.js](https://nodejs.org/ja) と[サーバー API を実行する](../../api/translations/README.ja.md)をインストールする必要があります。
|
||||
|
||||
ターミナルでこのコマンドを実行することで、サーバーが正常に動作していることを確認することができます。
|
||||
|
||||
```sh
|
||||
curl http://localhost:5000/api
|
||||
# -> 結果として "Bank API v1.0.0" を返す必要があります。
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## フォームとコントロール
|
||||
|
||||
`<form>` 要素は HTML ドキュメントのセクションをカプセル化し、ユーザがインタラクティブなコントロールを使ってデータを入力したり送信したりすることができます。フォームの中で利用できるユーザーインターフェイス (UI) コントロールには様々な種類がありますが、最も一般的なものは `<input>` と `<button>` 要素です。
|
||||
|
||||
例えば、ユーザがユーザ名を入力できるフィールドを作成するには、`<input>` 要素を使用することができます。
|
||||
|
||||
```html
|
||||
<input id="username" name="username" type="text">
|
||||
```
|
||||
|
||||
`name` 属性はフォームデータを送信する際のプロパティ名として使われます。`id` 属性は `<label>` をフォームコントロールに関連付けるために使われます。
|
||||
|
||||
> UI を構築する際に使用できるすべてのネイティブ UI 要素のアイデアを得るために、[`<input>` タイプ](https://developer.mozilla.org/ja/docs/Web/HTML/Element/input) と [その他のフォームコントロール](https://developer.mozilla.org/ja/docs/Learn/Forms/Other_form_controls) のリスト全体を見てみましょう。
|
||||
|
||||
✅ `<input>` は [空の要素](https://developer.mozilla.org/ja/docs/Glossary/Empty_element) であることに注意してください。それは一致するクロージングタグを追加すべき*ではありません*。タグ自身で閉じる `<input/>` 記法を使うことはできますが、必須ではありません。
|
||||
|
||||
フォーム内の `<button>` 要素は少し特殊です。`type` 属性を指定しないと、ボタンが押されたときに自動的にフォームデータをサーバに送信します。以下に可能な `type` の値を示します。
|
||||
|
||||
- `submit`: `<form>`内のデフォルトの値で、ボタンはフォームの送信アクションをトリガーします
|
||||
- `reset`: ボタンはすべてのフォームコントロールを初期値にリセットします
|
||||
- `button`: ボタンが押されたときのデフォルトの動作を割り当てないでください。その後、JavaScript を使ってカスタムアクションを割り当てることができます
|
||||
|
||||
### タスク
|
||||
|
||||
まずは `login` テンプレートにフォームを追加してみましょう。*ユーザ名*フィールドと*ログイン*ボタンが必要です。
|
||||
|
||||
```html
|
||||
<template id="login">
|
||||
<h1>Bank App</h1>
|
||||
<section>
|
||||
<h2>Login</h2>
|
||||
<form id="loginForm">
|
||||
<label for="username">Username</label>
|
||||
<input id="username" name="user" type="text">
|
||||
<button>Login</button>
|
||||
</form>
|
||||
</section>
|
||||
</template>
|
||||
```
|
||||
|
||||
よく見ると、ここに `<label>` 要素が追加されていることがわかります。`<label>` 要素はユーザー名フィールドなどの UI コントロールに名前を追加するために使われます。ラベルはフォームを読みやすくするために重要ですが、それだけではありません。
|
||||
|
||||
- ラベルをフォームコントロールに関連付けることで、(スクリーンリーダーのような) 支援技術を使用しているユーザーが、どのようなデータを提供することが求められているのかを理解するのに役立ちます
|
||||
- ラベルをクリックすると、関連する入力に直接フォーカスを当てることができるので、タッチスクリーンベースのデバイスでも手が届きやすくなります
|
||||
|
||||
> ウェブ上の [アクセシビリティ](https://developer.mozilla.org/ja/docs/Learn/Accessibility/What_is_accessibility) は、見落とされがちな非常に重要なトピックです。[セマンティックな HTML 要素](https://developer.mozilla.org/ja/docs/Learn/Accessibility/HTML) のおかげで、適切に使用すれば、アクセシブルなコンテンツを作成することは難しくありません。[アクセシビリティについての詳細を読む](https://developer.mozilla.org/ja/docs/Web/Accessibility)ことで、よくある間違いを回避し、責任ある開発者になることができます。
|
||||
|
||||
あとは、前のもののすぐ下に登録用の第二形態を追加します。
|
||||
|
||||
```html
|
||||
<hr/>
|
||||
<h2>Register</h2>
|
||||
<form id="registerForm">
|
||||
<label for="user">Username</label>
|
||||
<input id="user" name="user" type="text">
|
||||
<label for="currency">Currency</label>
|
||||
<input id="currency" name="currency" type="text" value="$">
|
||||
<label for="description">Description</label>
|
||||
<input id="description" name="description" type="text">
|
||||
<label for="balance">Current balance</label>
|
||||
<input id="balance" name="balance" type="number" value="0">
|
||||
<button>Register</button>
|
||||
</form>
|
||||
```
|
||||
|
||||
`value` 属性を用いて、与えられた入力に対してデフォルト値を定義することができます。
|
||||
また、`balance` の入力が `number` 型であることにも注目してください。他の入力とは違うように見えますか? これを使ってみてください。
|
||||
|
||||
✅ キーボードだけでフォームをナビゲートして対話することができますか? あなたならどうしますか?
|
||||
|
||||
## サーバーへのデータ送信
|
||||
|
||||
機能的な UI ができたので、次のステップはデータをサーバーに送信することです。現在のコードを使って簡単なテストをしてみましょう。*ログイン*または*登録*ボタンをクリックするとどうなりますか?
|
||||
|
||||
ブラウザの URL セクションの変化に気付きましたか?
|
||||
|
||||

|
||||
|
||||
`<form>` のデフォルトのアクションは、フォームを現在のサーバの URL に [GET メソッド](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3) を使って送信し、フォームのデータを URL に直接追加することです。この方法にはいくつかの欠点があります。
|
||||
|
||||
- 送信されるデータのサイズが非常に限られています (2000 文字程度)
|
||||
- データは URL で直接見ることができます (パスワードには向いていません)
|
||||
- ファイルのアップロードでは動作しません
|
||||
|
||||
そのため、これまでの制限を受けずに、HTTP リクエストの本文でフォームデータをサーバに送信する [POST メソッド](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5)を利用するように変更することができます。
|
||||
|
||||
> POST はデータを送信するために最も一般的に使用される方法ですが、[いくつかの特定のシナリオ](https://www.w3.org/2001/tag/doc/whenToUseGet.html)では、例えば検索フィールドを実装する場合には、GET メソッドを使用することが望ましいです。
|
||||
|
||||
### タスク
|
||||
|
||||
登録フォームに `action` と `method` プロパティを追加します。
|
||||
|
||||
```html
|
||||
<form id="registerForm" action="//localhost:5000/api/accounts" method="POST">
|
||||
```
|
||||
|
||||
ここで、あなたの名前で新しいアカウントを登録してみてください。*登録*ボタンをクリックすると、このような画面が表示されるはずです。
|
||||
|
||||

|
||||
|
||||
すべてがうまくいけば、サーバーはあなたのリクエストに、作成されたアカウントデータを含む [JSON](https://www.json.org/json-ja.html) レスポンスを返すはずです。
|
||||
|
||||
✅ 同じ名前で再度登録してみてください。どうなるでしょうか?
|
||||
|
||||
## ページを再読み込みせずにデータを送信する
|
||||
|
||||
お気づきのように、先ほど使用したアプローチに若干の問題があります。フォームを送信するときです。これが発生するとアプリを終了し、ブラウザはサーバーの URL にリダイレクトします。[シングルページアプリケーション(SPA)](https://en.wikipedia.org/wiki/Single-page_application) を作成しているので、ウェブアプリですべてのページのリロードを回避しようとしています。
|
||||
|
||||
ページのリロードを強制せずにフォームデータをサーバに送信するには、JavaScript のコードを使用しなければなりません。`<form>` 要素の `action` プロパティに URL を記述する代わりに、カスタムアクションを実行するために `javascript:` 文字列の前に任意の JavaScript コードを使用することができます。これを使うと、これまでブラウザが自動的に行っていたタスクを実装しなければならないことになります。
|
||||
|
||||
- フォームデータを取得する
|
||||
- フォームデータを適切なフォーマットに変換してエンコードする
|
||||
- HTTPリクエストを作成してサーバーに送信する
|
||||
|
||||
### タスク
|
||||
|
||||
登録フォームの `action` は、次のように置き換えてください。
|
||||
|
||||
```html
|
||||
<form id="registerForm" action="javascript:register()">
|
||||
```
|
||||
|
||||
`app.js`を開いて `register` という名前の関数を追加します。
|
||||
|
||||
```js
|
||||
function register() {
|
||||
const registerForm = document.getElementById('registerForm');
|
||||
const formData = new FormData(registerForm);
|
||||
const data = Object.fromEntries(formData);
|
||||
const jsonData = JSON.stringify(data);
|
||||
}
|
||||
```
|
||||
|
||||
ここでは、`getElementById()`を使ってフォーム要素を取得し、[`FormData`](https://developer.mozilla.org/ja/docs/Web/API/FormData) ヘルパーを使ってフォームコントロールからキーと値のペアのセットとして値を抽出します。次に、[`Object.fromEntries()`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/fromEntries) を使用してデータを通常のオブジェクトに変換し、最後に Web 上でデータを交換するために一般的に使用されるフォーマットである [JSON](https://www.json.org/json-ja.html) にデータをシリアライズします。
|
||||
|
||||
これで、データをサーバに送信する準備ができました。`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' };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
この関数は何をしているのでしょうか?まず、ここでの `async` キーワードに注目してください。これは、この関数が [**非同期的に**](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/async_function) を実行するコードを含んでいることを意味します。await` キーワードと一緒に使用すると、非同期コードが実行されるのを待つことができます。
|
||||
|
||||
以下に、`async/await` の使用法についての簡単なビデオを示します。
|
||||
|
||||
[](https://youtube.com/watch?v=YwmlRkrxvkk "Async and Await for managing promises")
|
||||
|
||||
JSON データをサーバに送信するには、`fetch()` API を使用します。このメソッドは2つのパラメータを受け取ります。
|
||||
|
||||
- サーバの URL なので、ここに `/localhost:5000/api/accounts` を返します
|
||||
- リクエストの設定。ここでメソッドを `POST` に設定し、リクエストのための `body` を提供します。JSON データをサーバに送るので、`Content-Type` ヘッダを `application/json` に設定する必要があります
|
||||
|
||||
サーバはリクエストに対して JSON で応答するので、`await response.json()` を使って JSON の内容を解析し、結果のオブジェクトを返すことができます。このメソッドは非同期なので、返す前に `await` キーワードを使って、解析中のエラーが発生した場合にはそれもキャッチするようにしています。
|
||||
|
||||
次に、`register` 関数にコードを追加して `createAccount()` を呼び出すようにします。
|
||||
|
||||
```js
|
||||
const result = await createAccount(jsonData);
|
||||
```
|
||||
|
||||
ここでは `await` キーワードを使用しているので、レジスタ関数の前に `async` キーワードを追加する必要があります。
|
||||
|
||||
```js
|
||||
async function register() {
|
||||
```
|
||||
|
||||
最後に、結果を確認するためのログを追加してみましょう。最終的な関数は以下のようになるはずです。
|
||||
|
||||
```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);
|
||||
}
|
||||
```
|
||||
|
||||
ちょっと長かったですが、無事にたどり着きました! [ブラウザの開発者ツール](https://developer.mozilla.org/ja/docs/Learn/Common_questions/What_are_browser_developer_tools)を開いて、新しいアカウントを登録してみると、Web ページには何も変化がないはずなのですが、コンソールにはすべてが正常に動作することを確認するメッセージが表示されます。
|
||||
|
||||

|
||||
|
||||
✅ データは安全にサーバーに送られていると思いますか? もし何者かにリクエストを傍受されたらどうしますか? 安全なデータ通信については、[HTTPS](https://ja.wikipedia.org/wiki/HTTPS) を読むとより詳しく知ることができます。
|
||||
|
||||
## データの検証
|
||||
|
||||
最初にユーザー名を設定せずに新規アカウントを登録しようとすると、サーバーがステータスコード [400 (Bad Request)](https://developer.mozilla.org/ja/docs/Web/HTTP/Status/400) でエラーを返していることがわかります。
|
||||
|
||||
サーバーにデータを送信する前に、可能な限り事前に [フォームデータの検証](https://developer.mozilla.org/ja/docs/Learn/Forms/Form_validation) を行い、有効なリクエストを送信していることを確認するのが良い方法です。HTML5 フォームコントロールは、様々な属性を使った組み込みのバリデーションを提供しています。
|
||||
|
||||
- `required`: フィールドには入力する必要があります。そうでない場合は、フォームを送信することができません
|
||||
- `minlength` と `maxlength`: テキストフィールドの最小文字数と最大文字数を定義します
|
||||
- `min` と `max`: 数値フィールドの最小値と最大値を定義します
|
||||
- `type`: `number`, `email`, `file` や [その他の組み込み型](https://developer.mozilla.org/ja/docs/Web/HTML/Element/input) のような、期待されるデータの種類を定義します。この属性はフォームコントロールの視覚的なレンダリングを変更することもできます
|
||||
- `pattern`: これを使用すると、入力されたデータが有効かどうかをテストするための [正規表現](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Regular_Expressions) パターンを定義することができます
|
||||
|
||||
> ヒント: CSS 疑似クラス `:valid` と `:invalid` を利用して、フォームコントロールの見た目を有効か無効かによってカスタマイズすることができます。
|
||||
|
||||
### タスク
|
||||
|
||||
有効な新規アカウントを作成するためには、ユーザー名と通貨の2つの必須フィールドがあり、その他のフィールドは任意です。フォームの HTML を更新し、`required` 属性とフィールドのラベルのテキストの両方を使用してください。
|
||||
|
||||
```html
|
||||
<label for="user">Username (required)</label>
|
||||
<input id="user" name="user" type="text" required>
|
||||
...
|
||||
<label for="currency">Currency (required)</label>
|
||||
<input id="currency" name="currency" type="text" value="$" required>
|
||||
```
|
||||
|
||||
このサーバの実装ではフィールドの最大長に特定の制限はありませんが、ユーザのテキスト入力に対して合理的な制限を定義することは常に良い習慣です。
|
||||
|
||||
テキストフィールドに `maxlength` 属性を追加します。
|
||||
|
||||
```html
|
||||
<input id="user" name="user" type="text" maxlength="20" required>
|
||||
...
|
||||
<input id="currency" name="currency" type="text" value="$" maxlength="5" required>
|
||||
...
|
||||
<input id="description" name="description" type="text" maxlength="100">
|
||||
```
|
||||
|
||||
これで、*Register* ボタンを押したときに、フィールドが定義したバリデーションルールに準拠していない場合は、次のように表示されるはずです。
|
||||
|
||||

|
||||
|
||||
このように、サーバにデータを送信する前に実行されるバリデーションのことを **クライアントサイド** のバリデーションと呼びます。しかし、データを送信せずにすべてのチェックを実行できるとは限らないことに注意してください。例えば、サーバーにリクエストを送らずに、同じユーザー名のアカウントが既に存在するかどうかを確認することはできません。サーバー上で実行される追加のバリデーションは、**サーバーサイド**のバリデーションと呼ばれます。
|
||||
|
||||
通常は両方を実装する必要があり、クライアントサイドのバリデーションを使用すると、ユーザーへのフィードバックを即座に提供することでユーザーエクスペリエンスが向上しますが、サーバーサイドのバリデーションは、操作するユーザーデータが健全で安全であることを確認するために非常に重要です。
|
||||
|
||||
---
|
||||
|
||||
## 🚀 チャレンジ
|
||||
|
||||
ユーザーが既に存在する場合には、エラーメッセージを HTML に表示します。
|
||||
|
||||
ここでは、少しのスタイリングの後に最終的なログインページがどのように見えるかの例を示します。
|
||||
|
||||

|
||||
|
||||
## レッスン後の小テスト
|
||||
|
||||
[レッスン後の小テスト](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/44?loc=ja)
|
||||
|
||||
## 復習と自己学習
|
||||
|
||||
開発者は、フォーム構築の取り組み、特に検証戦略に関して、非常にクリエイティブになっています。[CodePen](https://codepen.com) を見て、さまざまなフォームの流れについて学びましょう。
|
||||
|
||||
## 課題
|
||||
|
||||
[銀行アプリのスタイル設定](assignment.ja.md)
|
@@ -1,290 +0,0 @@
|
||||
# 은행 앱 제작하기 파트 2: 로그인과 가입 폼 작성하기
|
||||
|
||||
## 강의 전 퀴즈
|
||||
|
||||
[Pre-lecture quiz](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/43?loc=ko)
|
||||
|
||||
### 소개
|
||||
|
||||
모든 모던 웹 앱에서 대부분은, 자신의 개인 공간을 가질 계정을 만들 수 있습니다. 여러 사용자가 동시에 웹 앱에 접근할 수 있으므로, 각자 사용자의 개인 데이터를 별도로 저장하고 어느 정보를 보여줄 지에 대하여 선택하는 메커니즘이 필요합니다. 자체적으로 광범위한 주제이므로 [user identity securely](https://en.wikipedia.org/wiki/Authentication) 관리하는 방법은 다루지 않지만, 각자가 앱에서 하나 (이상)의 은행 계좌를 만들 수 있는지 확인합니다.
|
||||
|
||||
이 파트에서는 HTML 폼으로 웹 앱에 로그인과 가입을 추가합니다. 프로그래밍 방식으로 데이터를 서버 API에 보내는 방법과, 최종적으로 사용자 입력에 대한 기본 유효성 검사 규칙을 정의하는 방법에 대해 보겠습니다.
|
||||
|
||||
### 준비물
|
||||
|
||||
이 강의를 위해 웹 앱의 [HTML templates and routing](../../1-template-route/translations/README.ko.md)을 완료해야합니다. 또한 [Node.js](https://nodejs.org)와 [run the server API](../../api/README.md)를 로컬에 설치해야 계정을 만들 데이터를 보낼 수 있습니다.
|
||||
|
||||
터미널에서 다음 명령을 실행하여 서버가 잘 실행되고 있는지 테스트할 수 있습니다:
|
||||
|
||||
```sh
|
||||
curl http://localhost:5000/api
|
||||
# -> should return "Bank API v1.0.0" as a result
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 폼과 컨트롤
|
||||
|
||||
`<form>` 요소는 사용자가 대화형 컨트롤을 사용하여 데이터를 입력하고 제출할 수 있는 HTML 문서의 섹션을 캡슐화합니다. 폼 내에서 쓸 수 있는 모든 종류의 사용자 인터페이스(UI) 컨트롤이 있으며, 가장 일반적인 컨트롤은 `<input>`과 `<button>` 요소입니다.
|
||||
|
||||
`<input>`에는 다양한 [types](https://developer.mozilla.org/docs/Web/HTML/Element/input)이 많이 있습니다, 예를 들어 사용자 이름으로 입력 가능한 필드를 만들려면 다음과 같이 사용할 수 있습니다:
|
||||
|
||||
```html
|
||||
<input name="username" type="text">
|
||||
```
|
||||
|
||||
`name` 속성은 컨트롤을 식별하는 데 사용되고 폼 데이터를 전송할 때 속성 이름으로 사용됩니다.
|
||||
|
||||
> UI를 작성할 때 쓸 수 있는 모든 네이티브 UI 요소에 대한 아이디어를 얻으려면 [`<input>` types](https://developer.mozilla.org/docs/Web/HTML/Element/input)과 [other form controls](https://developer.mozilla.org/docs/Learn/Forms/Other_form_controls)의 전체 목록을 찾아봅시다.
|
||||
|
||||
✅ `<input>`은 닫는 태그를 맞추지 *않는* [empty element](https://developer.mozilla.org/docs/Glossary/Empty_element)입니다. 자동으로-닫는 `<input/>` 표기법을 사용할 수 있지만, 필수는 아닙니다.
|
||||
|
||||
폼 내의 `<button>` 요소는 약간 특별합니다. `type` 속성을 지정하지 않으면, 눌렀을 때 폼 데이터가 자동으로 서버에 제출됩니다. 가능한 `type` 값은 다음과 같습니다:
|
||||
|
||||
- `submit`: `<form>`내의 기본값이며, 버튼은 폼 제출 작업으로 연결합니다.
|
||||
- `reset`: 버튼은 모든 폼 컨트롤을 초기 값으로 다시 설정합니다.
|
||||
- `button`: 버튼을 눌렀을 때 기본 동작을 지정하지 않습니다. JavaScript를 사용하여 커스텀 작업을 할당할 수 있습니다.
|
||||
|
||||
### 작업
|
||||
|
||||
`login` 템플릿에 폼을 추가하는 것으로 시작하겠습니다. *username* 필드와 *Login* 버튼이 필요합니다.
|
||||
|
||||
```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>
|
||||
```
|
||||
|
||||
자세히 살펴보면, 여기에 `<label>` 요소도 추가된 것을 알 수 있습니다. `<label>`은 username 필드와 같은, UI 컨트롤의 캡션을 추가하는 데 사용됩니다. 라벨은 폼의 가독성을 위해서 중요하지만, 추가적인 장점도 제공합니다:
|
||||
|
||||
- 라벨을 폼 컨트롤에 연결하면, (화면 판독기와 같은) 보조 기술을 사용하는 사용자가 받는 것으로 예상되는 데이터를 이해하는 데 도움이 됩니다.
|
||||
- 라벨을 클릭하여 연결된 입력에 직접 맞출 수 있으므로, 터치-스크린 기반 장치에서 더 쉽게 접근할 수 있습니다.
|
||||
|
||||
> 웹에서의 [Accessibility](https://developer.mozilla.org/docs/Learn/Accessibility/What_is_accessibility)은 종종 간과되는 매우 중요한 주제입니다. [HTML5 semantic elements](https://developer.mozilla.org/docs/Learn/Accessibility/HTML) 덕분에 이를 적절하게 사용한다면 접근성 콘텐츠로 만드는 것은 어렵지 않습니다. 일반적인 실수를 피하고 책임있는 개발자가 되기 위해 [accessibility에 대하여 읽을 수](https://developer.mozilla.org/docs/Web/Accessibility) 있습니다.
|
||||
|
||||
이제 이전 항목의 바로 아래에, 가입을 위한 두번째 폼을 추가합니다:
|
||||
|
||||
```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>
|
||||
```
|
||||
|
||||
`value` 속성을 사용하여 주어진 입력에 대한 기본값을 정의할 수 있습니다.
|
||||
`balance`에 대한 입력에는 `number` 타입이 존재 합니다. 다른 입력과 다르게 보이나요? 상호작용 해보세요.
|
||||
|
||||
✅ 키보드만 사용하여 폼을 탐색하고 상호 작용할 수 있나요? 어떻게 하나요?
|
||||
|
||||
## 서버에 데이터 제출하기
|
||||
|
||||
이제 기능 UI가 있으므로, 다음 단계는 데이터를 서버로 보내는 것입니다. 현재 코드를 사용하여 간단한 테스트를 해봅시다. *Login* 혹은 *Register* 버튼을 클릭하면 어떻게 되나요?
|
||||
|
||||
브라우저의 URL 섹션에서 변경된 것을 알고 있나요?
|
||||
|
||||

|
||||
|
||||
`<form>`의 기본 작업은 [GET method](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3)를 사용하여 현재 서버 URL에 폼을 제출하고, 폼 데이터를 URL에 직접 추가하는 것입니다. 이 방식에는 몇 가지 단점이 있습니다:
|
||||
|
||||
- 전송되는 데이터는 크기가 매우 제한적입니다 (2000 자)
|
||||
- 데이터가 URL에 직접 보입니다 (비밀번호에 적절하지 않습니다)
|
||||
- 파일 업로드는 작동하지 않습니다
|
||||
|
||||
그러므로 아무런 제한없이 하려면, HTTP 요청 본문에서 폼 데이터를 서버로 보내는 [POST method](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5)를 사용하게 변경할 수 있습니다.
|
||||
|
||||
> POST는 데이터를 보낼 때 가장 일반적인 방식이지만, [some specific scenarios](https://www.w3.org/2001/tag/doc/whenToUseGet.html)에서 검색 필드를 구현할 때는, GET 방법을 사용하는 것이 더 좋습니다.
|
||||
|
||||
### 작업
|
||||
|
||||
가입 폼에 `action`과 `method` 속성을 추가합니다:
|
||||
|
||||
```html
|
||||
<form id="registerForm" action="//localhost:5000/api/accounts" method="POST">
|
||||
```
|
||||
|
||||
이제 이름으로 새로운 계정을 가입합니다. *Register* 버튼을 클릭하면 다음과 같은 내용이 표시됩니다:
|
||||
|
||||

|
||||
|
||||
모든 것이 잘 되면, 서버에 생성된 계정 데이터가 포함되어 [JSON](https://www.json.org/json-en.html)으로 응답해야 합니다.
|
||||
|
||||
✅ 같은 이름으로 다시 가입해보세요. 무슨 일이 일어났나요?
|
||||
|
||||
## 페이지를 다시 불러오지 않고 데이터 제출하기
|
||||
|
||||
알다시피, 사용한 접근 방식에는 약간 이슈가 있습니다: 폼을 제출할 때, 앱에서 나가면서 브라우저가 서버 URL로 리디렉션됩니다. [Single-page application (SPA)](https://en.wikipedia.org/wiki/Single-page_application)을 만들고 있으므로, 웹 앱으로 모든 페이지를 다시 불러오지 않으려 합니다.
|
||||
|
||||
페이지를 강제로 다시 불러오지 않고 폼 데이터를 서버로 보내려면, JavaScript 코드를 사용해야 합니다. `<form>` 요소의 `action` 속성에 URL을 넣는 대신, `javascript:` 문자열이 앞에 붙은 JavaScript 코드를 사용하여 커스텀 작업을 할 수 있습니다. 이를 사용하면 이전에 끝낸 브라우저로 자동 수행한 일부 작업을 구현해야 합니다:
|
||||
|
||||
- 폼 데이터 검색하기
|
||||
- 폼 데이터를 알맞은 포맷으로 변환하고 인코딩하기
|
||||
- HTTP 요청을 생성하고 서버로 전송하기
|
||||
|
||||
### 작업
|
||||
|
||||
가입 폼 `action`을 다음으로 바꿉니다:
|
||||
|
||||
```html
|
||||
<form id="registerForm" action="javascript:register()">
|
||||
```
|
||||
|
||||
`app.js` 열어서 `register`라고 지어진 새로운 함수를 추가합니다:
|
||||
|
||||
```js
|
||||
function register() {
|
||||
const registerForm = document.getElementById('registerForm');
|
||||
const formData = new FormData(registerForm);
|
||||
const data = Object.fromEntries(formData);
|
||||
const jsonData = JSON.stringify(data);
|
||||
}
|
||||
```
|
||||
|
||||
여기서는 `getElementById()`를 사용하여 폼 요소를 검색하고, [`FormData`](https://developer.mozilla.org/docs/Web/API/FormData) 헬퍼를 사용하여 키/값 쌍 집합으로 폼 컨트롤에서 값을 추출합니다. 그러고 [`Object.fromEntries()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/fromEntries)를 사용하여 데이터를 일반 객체로 변환하여 최종적으로 웹에서 데이터를 교환할 때, 일반적으로 사용되는 포맷인 [JSON](https://www.json.org/json-en.html)으로 데이터를 직렬화합니다.
|
||||
|
||||
데이터는 이제 서버에 보낼 준비가 되었습니다. `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' };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
이 함수는 어떤 일을 할까요? 먼저, 여기있는 `async` 키워드를 확인하세요. 이 함수는 [**asynchronously**](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/async_function)하게 실행하는 코드가 포함되어 있다는 것을 의미합니다. `await` 키워드와 함께 사용하면, 비동기 코드가 실행될 때까지 기다릴 수 있습니다 - 여기에서 서버의 응답을 기다리는 것과 같습니다 - 계속하기 전에.
|
||||
|
||||
여기는 ``async/await` 사용 방식에 대한 간단한 영상입니다:
|
||||
|
||||
[](https://youtube.com/watch?v=YwmlRkrxvkk "Async and Await for managing promises")
|
||||
|
||||
`fetch()` API를 사용하여 JSON 데이터를 서버로 보냅니다. 이 메소드는 2개의 파라미터가 사용됩니다:
|
||||
|
||||
- 서버의 URL이므로, 여기에 `//localhost:5000/api/accounts`를 다시 넣습니다.
|
||||
- 요청의 설정입니다. 여기서 메소드를 `POST`로 설정하고 요청한 `body`를 줍니다. JSON 데이터를 서버로 보낼 때, `Content-Type` 헤더를 `application/json`으로 설정하여 서버가 인터프리터하는 방식을 알 수 있도록 합니다.
|
||||
|
||||
서버가 JSON으로 응답하므로, `await response.json()`을 사용하여 JSON 콘텐츠를 파싱하고 결과 객체를 반환할 수 있습니다. 이 메소드는 비동기이므로, 반환하기 전 여기에서 `await` 키워드를 사용하여 파싱하는 도중에도 오류가 발생하는지 확인합니다.
|
||||
|
||||
이제 `register` 함수에 코드를 추가하여 `createAccount()`를 호출합니다:
|
||||
|
||||
```js
|
||||
const result = await createAccount(jsonData);
|
||||
```
|
||||
|
||||
`await` 함수를 여기에서 사용하기 때문에, 가입 함수 전에 `async` 키워드를 추가해야 합니다:
|
||||
|
||||
```js
|
||||
async function register() {
|
||||
```
|
||||
|
||||
마지막으로, 결과를 보기 위해서 로그를 추가하겠습니다. 최종 함수은 다음과 같습니다:
|
||||
|
||||
```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);
|
||||
}
|
||||
```
|
||||
|
||||
조금 길지만 도착했습니다! [browser developer tools](https://developer.mozilla.org/docs/Learn/Common_questions/What_are_browser_developer_tools)를 열고, 새 계정을 가입하면, 웹 페이지에 변경 사항이 표시되지 않으면서 콘솔에 작동을 확인할 메시지가 나타납니다.
|
||||
|
||||

|
||||
|
||||
✅ 데이터가 안전하게 서버로 보내졌다고 생각하나요? 누군가 요청을 가져갈 수 있다면 어떤가요? 보안 데이터 통신에 대해 자세히 보려면 [HTTPS](https://en.wikipedia.org/wiki/HTTPS)를 읽어보세요.
|
||||
|
||||
## Data 검증하기
|
||||
|
||||
사용자 이름을 먼저 설정하지 않고 새 계정을 가입하려하면, 서버에서 상태 코드 [400 (Bad Request)](https://developer.mozilla.org/docs/Web/HTTP/Status/400#:~:text=The%20HyperText%20Transfer%20Protocol%20(HTTP,%2C%20or%20deceptive%20request%20routing).) 오류를 반환하는 것으로 볼 수 있습니다.
|
||||
|
||||
데이터를 서버로 보내기 전에 할 수 있다면, 유요한 요청을 보낼 수 있도록, 미리 [validate the form data](https://developer.mozilla.org/docs/Learn/Forms/Form_validation)를 실습하는 것이 좋습니다. HTML5 포맷 컨트롤은 다양한 속성을 사용하여 빌트인 유효성 검사를 제공합니다:
|
||||
|
||||
- `required`: 필드를 채워야하며 안 채운다면 폼을 제출할 수 없습니다.
|
||||
- `minlength`와 `maxlength`: 텍스트 입력의 최소 및 최대 문자 수를 정의합니다.
|
||||
- `min`과 `max`: 숫자 필드의 최소값과 최대값을 정의합니다.
|
||||
- `type`: `number`, `email`, `file` 또는 [other built-in types](https://developer.mozilla.org/docs/Web/HTML/Element/input)처럼, 예상되는 데이터의 종류를 정의합니다. 이 속성은 폼 컨트롤의 비주얼 렌더링을 바꿀 수도 있습니다.
|
||||
- `pattern`: 입력된 데이터가 유효한지 테스트하기 위해 [regular expression](https://developer.mozilla.org/docs/Web/JavaScript/Guide/Regular_Expressions) 패턴을 정의할 수 있습니다.
|
||||
|
||||
> Tip: 유효하거나 `:valid`와 `:invalid` CSS pseudo-classes를 사용하지 않는 여부에 따라 폼 컨트롤의 모양을 커스텀할 수 있습니다.
|
||||
|
||||
### 작업
|
||||
|
||||
유효한 새로운 계정을 만들 때에 username과 currency라는 2개의 필수 필드가 필요하며, 다른 필드는 옵션입니다. HTML에서 폼을 갱신하여 다음 사항을 반영합니다:
|
||||
|
||||
```html
|
||||
<input name="user" type="text" required>
|
||||
...
|
||||
<input name="currency" type="text" value="$" required>
|
||||
```
|
||||
|
||||
이 특정 서버을 구현하는 것은 필드의 최대 길이에 제한을 걸진 않지만, 항상 사용자 텍스트 항목에 대하여 적당한 제한을 두는 것이 좋습니다.
|
||||
|
||||
`maxlength` 속성을 이 텍스트 필드에 추가합니다:
|
||||
|
||||
```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">
|
||||
```
|
||||
|
||||
이제 *Register* 버튼을 누르고 정의한 유효성 검사 규칙을 필드가 따르지 않는 경우에는, 다음과 같이 보입니다:
|
||||
|
||||

|
||||
|
||||
서버에 데이터를 보내기 *전에 하는* 유효성 검사를 **client-side** 유효성 검사라고 합니다. 그러나 데이터를 보내지 않고 모든 검사를 하는 것은 항상 가능하지 않습니다. 예를 들면, 서버에 요청을 보내지 않고 동일한 사용자 이름을 가진 계정이 이미 존재하는지 확인할 수 있는 방식은 없습니다. 서버에서 수행되는 추가적인 유효성 검사를 **server-side** 유효성 검사라고합니다.
|
||||
|
||||
일반적으로 모두 구현해야하며, 클라이언트-측 유효성 검사를 사용하면 사용자에게 즉시 피드백을 주고 사용자 경험도 향상되지만, 서버-측 유효성 검사도 바뀌는 사용자 데이터가 건전하고 안전한지 확인하려면 중요합니다.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 도전
|
||||
|
||||
이미 사용자가 존재한다면 HTML 오류 메시지가 나옵니다.
|
||||
|
||||
다음은 살짝 스타일을 적용한 뒤에 최종 로그인 페이지를 보여주는 예시입니다:
|
||||
|
||||

|
||||
|
||||
## 강의 후 퀴즈
|
||||
|
||||
[Post-lecture quiz](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/44?loc=ko)
|
||||
|
||||
## 리뷰 & 자기주도 학습
|
||||
|
||||
개발자는 특히 유효성 검사 전략과 관련하여, 폼을 작성하는 노력에 대해 매우 창의적으로 생각했습니다. [CodePen](https://codepen.com)으로 다양한 폼 흐름에 대해 알아보세요; 흥미롭고 영감이 생기는 폼을 찾을 수 있나요?
|
||||
|
||||
## 과제
|
||||
|
||||
[Style your bank app](../assignment.md)
|
@@ -1,294 +0,0 @@
|
||||
# Bina Aplikasi Perbankan Bahagian 2: Bina Log Masuk dan Borang Pendaftaran
|
||||
|
||||
# Kuiz Pra Kuliah
|
||||
|
||||
[Kuiz Pra Kuliah](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/43)
|
||||
|
||||
### Pengenalan
|
||||
|
||||
Di hampir semua aplikasi web moden, anda boleh membuat akaun untuk mempunyai ruang peribadi anda sendiri. Oleh kerana banyak pengguna dapat mengakses aplikasi web pada masa yang sama, anda memerlukan mekanisme untuk menyimpan data peribadi setiap pengguna secara berasingan dan memilih maklumat yang akan ditampilkan. Kami tidak akan membahas bagaimana menguruskan [identiti pengguna dengan selamat](https://en.wikipedia.org/wiki/Authentication) kerana ia adalah topik yang luas sendiri, tetapi kami akan memastikan setiap pengguna dapat membuatnya (atau lebih) akaun bank di aplikasi kami.
|
||||
|
||||
Di bahagian ini kami akan menggunakan borang HTML untuk menambahkan log masuk dan pendaftaran ke aplikasi web kami. Kami akan melihat cara mengirim data ke API pelayan secara terprogram, dan akhirnya bagaimana menentukan peraturan pengesahan asas untuk input pengguna.
|
||||
|
||||
### Prasyarat
|
||||
|
||||
Anda perlu melengkapkan [templat HTML dan perutean](../../1-template-rute/README.md) aplikasi web untuk pelajaran ini. Anda juga perlu memasang [Node.js](https://nodejs.org) dan [jalankan API pelayan](../../api/README.md) secara tempatan supaya anda dapat menghantar data untuk membuat akaun.
|
||||
|
||||
Anda boleh menguji bahawa pelayan berjalan dengan betul dengan menjalankan perintah ini di terminal:
|
||||
|
||||
```sh
|
||||
curl http://localhost:5000/api
|
||||
# -> harus mengembalikan "Bank API v1.0.0" sebagai hasilnya
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Bentuk dan kawalan
|
||||
|
||||
Elemen `<form>` merangkumi bahagian dokumen HTML di mana pengguna dapat memasukkan dan menyerahkan data dengan kawalan interaktif. Terdapat pelbagai jenis kawalan antara muka pengguna (UI) yang dapat digunakan dalam bentuk, yang paling umum adalah elemen `<input>` dan elemen `<button>`.
|
||||
|
||||
Terdapat banyak [jenis yang berbeza](https://developer.mozilla.org/docs/Web/HTML/Element/input) dari `<input>`, misalnya untuk membuat bidang di mana pengguna boleh memasukkan nama penggunanya yang boleh anda gunakan:
|
||||
|
||||
```html
|
||||
<input id="username" name="username" type="text">
|
||||
```
|
||||
|
||||
Atribut `name` akan digunakan sebagai nama harta ketika data borang akan dihantar. Atribut `id` digunakan untuk mengaitkan` <label>` dengan kawalan bentuk.
|
||||
|
||||
> Lihat keseluruhan senarai [`<input>` types](https://developer.mozilla.org/docs/Web/HTML/Element/input) dan [kawalan bentuk lain](https://developer.mozilla.org/docs/Learn/Forms/Other_form_controls) untuk mendapatkan idea tentang semua elemen UI asli yang boleh anda gunakan semasa membina UI anda.
|
||||
|
||||
✅ Perhatikan bahawa `<input>` adalah [elemen kosong](https://developer.mozilla.org/docs/Glossary/Empty_element) di mana anda seharusnya *tidak* menambahkan tag penutup yang sepadan. Anda bagaimanapun boleh menggunakan notasi `<input/>` tutup sendiri, tetapi tidak diperlukan.
|
||||
|
||||
Elemen `<button>` dalam bentuk agak istimewa. Sekiranya anda tidak menentukan atribut `type`, ia akan secara automatik mengirimkan data borang ke pelayan ketika ditekan. Berikut adalah nilai `type` yang mungkin:
|
||||
|
||||
- `submit`: Lalai dalam `<form>`, butang memicu tindakan pengiriman borang.
|
||||
- `reset`: Butang menetapkan semula semua kawalan bentuk ke nilai awalnya.
|
||||
- `button`: Jangan berikan tingkah laku lalai semasa butang ditekan. Anda kemudian dapat menetapkan tindakan khusus untuknya menggunakan JavaScript.
|
||||
|
||||
### Tugas
|
||||
|
||||
Mari mulakan dengan menambahkan borang ke templat `login`. Kami memerlukan medan *nama pengguna* dan butang *Log masuk*.
|
||||
|
||||
```html
|
||||
<template id="login">
|
||||
<h1>Bank App</h1>
|
||||
<section>
|
||||
<h2>Login</h2>
|
||||
<form id="loginForm">
|
||||
<label for="username">Username</label>
|
||||
<input id="username" name="user" type="text">
|
||||
<button>Login</button>
|
||||
</form>
|
||||
</section>
|
||||
</template>
|
||||
```
|
||||
|
||||
Sekiranya anda melihat lebih dekat, anda dapat melihat bahawa kami juga menambahkan elemen `<label>` di sini. Elemen `<label>` digunakan untuk menambahkan nama ke kawalan UI, seperti bidang nama pengguna kami. Label penting untuk keterbacaan borang anda, tetapi juga dilengkapi dengan faedah tambahan:
|
||||
|
||||
- Dengan mengaitkan label dengan kawalan bentuk, ia membantu pengguna menggunakan teknologi bantu (seperti pembaca skrin) untuk memahami data apa yang diharapkan dapat mereka berikan.
|
||||
- Anda dapat mengklik label untuk memberi fokus langsung pada input yang berkaitan, sehingga lebih mudah dijangkau pada peranti berdasarkan layar sentuh.
|
||||
|
||||
> [Kebolehcapaian](https://developer.mozilla.org/docs/Learn/Accessibility/What_is_accessibility) di web adalah topik yang sangat penting yang sering diabaikan. Terima kasih kepada [elemen HTML semantik](https://developer.mozilla.org/docs/Learn/Accessibility/HTML) tidak sukar untuk membuat kandungan yang boleh diakses jika anda menggunakannya dengan betul. Anda boleh [baca lebih lanjut mengenai kebolehaksesan](https://developer.mozilla.org/docs/Web/Accessibility) untuk mengelakkan kesilapan biasa dan menjadi pembangun yang bertanggungjawab.
|
||||
|
||||
Sekarang kita akan menambah borang kedua untuk pendaftaran, tepat di bawah yang sebelumnya:
|
||||
|
||||
```html
|
||||
<hr/>
|
||||
<h2>Register</h2>
|
||||
<form id="registerForm">
|
||||
<label for="user">Username</label>
|
||||
<input id="user" name="user" type="text">
|
||||
<label for="currency">Currency</label>
|
||||
<input id="currency" name="currency" type="text" value="$">
|
||||
<label for="description">Description</label>
|
||||
<input id="description" name="description" type="text">
|
||||
<label for="balance">Current balance</label>
|
||||
<input id="balance" name="balance" type="number" value="0">
|
||||
<button>Register</button>
|
||||
</form>
|
||||
```
|
||||
|
||||
Dengan menggunakan atribut `value` kita dapat menentukan nilai lalai untuk input yang diberikan.
|
||||
Perhatikan juga bahawa input untuk `balance` mempunyai jenis `number`. Adakah ia kelihatan berbeza daripada input lain? Cuba berinteraksi dengannya.
|
||||
|
||||
✅ Bolehkah anda menavigasi dan berinteraksi dengan borang hanya menggunakan papan kekunci? Bagaimana anda akan melakukannya?
|
||||
|
||||
## Menyerahkan data ke pelayan
|
||||
|
||||
Sekarang kita mempunyai UI yang berfungsi, langkah seterusnya adalah mengirim data ke pelayan kita. Mari buat ujian cepat menggunakan kod kami sekarang: apa yang berlaku jika anda mengklik butang *Login* atau *Register*?
|
||||
|
||||
Adakah anda melihat perubahan pada bahagian URL penyemak imbas anda?
|
||||
|
||||

|
||||
|
||||
Tindakan lalai untuk `<form>` adalah menyerahkan borang ke URL pelayan semasa menggunakan [kaedah GET](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3 ), menambahkan data borang terus ke URL. Kaedah ini mempunyai beberapa kekurangan walaupun:
|
||||
|
||||
- Ukuran data yang dikirim sangat terhad (sekitar 2000 aksara)
|
||||
- Data dapat dilihat secara langsung di URL (tidak bagus untuk kata laluan)
|
||||
- Ia tidak berfungsi dengan muat naik fail
|
||||
|
||||
Itulah sebabnya anda boleh mengubahnya untuk menggunakan [kaedah POST](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5) yang menghantar data borang ke pelayan di badan permintaan HTTP, tanpa batasan sebelumnya.
|
||||
|
||||
> Walaupun POST adalah kaedah yang paling sering digunakan untuk mengirim data, [dalam beberapa senario tertentu](https://www.w3.org/2001/tag/doc/whenToUseGet.html) lebih baik menggunakan kaedah GET, semasa melaksanakan bidang carian misalnya.
|
||||
|
||||
### Tugas
|
||||
|
||||
Tambahkan sifat `action` dan `method` ke borang pendaftaran:
|
||||
|
||||
```html
|
||||
<form id="registerForm" action="//localhost:5000/api/accounts" method="POST">
|
||||
```
|
||||
|
||||
Sekarang cuba daftarkan akaun baru dengan nama anda. Setelah mengklik butang * Daftar * anda akan melihat sesuatu seperti ini:
|
||||
|
||||

|
||||
|
||||
Sekiranya semuanya berjalan lancar, pelayan harus menjawab permintaan anda dengan respons [JSON](https://www.json.org/json-en.html) yang mengandungi data akaun yang telah dibuat.
|
||||
|
||||
✅ Cuba daftar sekali lagi dengan nama yang sama. Apa yang berlaku?
|
||||
|
||||
## Mengirim data tanpa memuatkan semula halaman
|
||||
|
||||
Seperti yang anda perhatikan, ada sedikit masalah dengan pendekatan yang baru kami gunakan: semasa menghantar borang, kami keluar dari aplikasi kami dan penyemak imbas mengalihkan ke URL pelayan. Kami berusaha untuk mengelakkan semua muat semula halaman dengan aplikasi web kami, kerana kami sedang membuat [Aplikasi satu halaman (SPA)](https://en.wikipedia.org/wiki/Single-page_application).
|
||||
|
||||
Untuk mengirim data formulir ke pelayan tanpa memaksa muat ulang halaman, kita harus menggunakan kod JavaScript. Daripada meletakkan URL di properti `action` elemen `<form>`, Anda dapat menggunakan kod JavaScript apa pun yang disiapkan oleh string `javascript:` untuk melakukan tindakan khusus. Menggunakan ini juga bermaksud bahawa anda harus melaksanakan beberapa tugas yang sebelumnya dilakukan secara automatik oleh penyemak imbas:
|
||||
|
||||
- Dapatkan semula data borang
|
||||
- Tukar dan kodkan data bentuk ke format yang sesuai
|
||||
- Buat permintaan HTTP dan kirimkan ke pelayan
|
||||
|
||||
### Tugas
|
||||
|
||||
Ganti borang tindakan `action` dengan:
|
||||
|
||||
```html
|
||||
<form id="registerForm" action="javascript:register()">
|
||||
```
|
||||
|
||||
Buka `app.js` tambahkan fungsi baru bernama `register`:
|
||||
|
||||
```js
|
||||
function register() {
|
||||
const registerForm = document.getElementById('registerForm');
|
||||
const formData = new FormData(registerForm);
|
||||
const data = Object.fromEntries(formData);
|
||||
const jsonData = JSON.stringify(data);
|
||||
}
|
||||
```
|
||||
|
||||
Di sini kita mengambil elemen borang menggunakan `getElementById()` dan menggunakan pembantu [`FormData`](https://developer.mozilla.org/docs/Web/API/FormData) untuk mengekstrak nilai dari borang kawalan sebagai satu set pasangan kunci / nilai. Kemudian kami menukar data ke objek biasa menggunakan [`Object.fromEntries()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/fromEntries) dan akhirnya bersiri data ke [JSON](https://www.json.org/json-en.html), format yang biasa digunakan untuk pertukaran data di web.
|
||||
|
||||
Data kini siap dihantar ke pelayan. Buat fungsi baru bernama `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' };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Apakah fungsi ini? Pertama, perhatikan kata kunci `async` di sini. Ini bermaksud bahawa fungsi tersebut mengandungi kod yang akan menjalankan [**asynchronously**](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/async_function). Apabila digunakan di sepanjang kata kunci `await`, ia membolehkan menunggu kod tak segerak untuk dilaksanakan - seperti menunggu tindak balas pelayan di sini - sebelum meneruskan.
|
||||
|
||||
Berikut adalah video ringkas mengenai penggunaan `async/await`:
|
||||
|
||||
[](https://youtube.com/watch?v=YwmlRkrxvkk "Async dan Tunggu untuk mengurus janji")
|
||||
|
||||
> Klik gambar di atas untuk video mengenai async / waiting.
|
||||
|
||||
Kami menggunakan API `fetch()` untuk mengirim data JSON ke pelayan. Kaedah ini mengambil 2 parameter:
|
||||
|
||||
- URL pelayan, jadi kami meletakkan kembali `//localhost5000/api/akaun` di sini.
|
||||
- Tetapan permintaan. Di situlah kami menetapkan kaedah untuk `POST` dan memberikan `body` untuk permintaan tersebut. Semasa kami menghantar data JSON ke pelayan, kami juga perlu menetapkan tajuk `Content-Type` ke` application/json` sehingga pelayan tahu bagaimana menafsirkan isi.
|
||||
|
||||
Oleh kerana pelayan akan menjawab permintaan dengan JSON, kita dapat menggunakan `tunggu tunggu.json()` untuk mengurai kandungan JSON dan mengembalikan objek yang dihasilkan. Perhatikan bahawa kaedah ini tidak segerak, jadi kami menggunakan kata kunci `tunggu 'di sini sebelum kembali untuk memastikan sebarang ralat semasa penghuraian juga terperangkap.
|
||||
|
||||
Sekarang tambahkan beberapa kod ke fungsi `register` untuk memanggil `createAccount()`:
|
||||
|
||||
```js
|
||||
const result = await createAccount(jsonData);
|
||||
```
|
||||
|
||||
Kerana kita menggunakan kata kunci `await` di sini, kita perlu menambahkan kata kunci `async` sebelum fungsi daftar:
|
||||
|
||||
```js
|
||||
async function register() {
|
||||
```
|
||||
|
||||
Akhirnya, mari kita tambahkan beberapa log untuk memeriksa hasilnya. Fungsi terakhir kelihatan seperti ini:
|
||||
|
||||
```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);
|
||||
}
|
||||
```
|
||||
|
||||
Itu agak lama tetapi kami sampai di sana! Sekiranya anda membuka [alat pembangun penyemak imbas](https://developer.mozilla.org/docs/Learn/Common_questions/What_are_browser_developer_tools), dan cuba mendaftarkan akaun baru, anda tidak akan melihat perubahan di laman web tetapi mesej akan muncul di konsol yang mengesahkan bahawa semuanya berfungsi.
|
||||
|
||||

|
||||
|
||||
✅ Anda fikir data dihantar ke pelayan dengan selamat? Bagaimana jika seseorang yang dapat memintas permintaan itu? Anda boleh membaca mengenai [HTTPS](https://en.wikipedia.org/wiki/HTTPS) untuk mengetahui lebih lanjut mengenai komunikasi data yang selamat.
|
||||
|
||||
## Pengesahan data
|
||||
|
||||
Sekiranya anda cuba mendaftarkan akaun baru tanpa menetapkan nama pengguna terlebih dahulu, anda dapat melihat bahawa pelayan mengembalikan ralat dengan kod status [400 (Bad Request)](https://developer.mozilla.org/docs/Web/HTTP/Status/400#:~:text=The%20HyperText%20Transfer%20Protocol%20(HTTP,%2C%20or%20deceptive%20request%20routing).).
|
||||
|
||||
Sebelum menghantar data ke pelayan, adalah amalan yang baik untuk [mengesahkan data borang](https://developer.mozilla.org/docs/Learn/Forms/Form_validation) terlebih dahulu apabila mungkin, untuk memastikan anda menghantar permintaan yang sah. Kawalan borang HTML5 memberikan pengesahan terbina dalam menggunakan pelbagai atribut:
|
||||
|
||||
- `diperlukan`: bidang perlu diisi jika tidak, borang tidak dapat dihantar.
|
||||
- `minlength` dan` maxlength`: menentukan bilangan aksara minimum dan maksimum dalam bidang teks.
|
||||
- `min` dan `max`: menentukan nilai minimum dan maksimum medan angka.
|
||||
- `type`: mentakrifkan jenis data yang diharapkan, seperti `number`, `email`, `file` atau [other built-in types](https://developer.mozilla.org/docs/Web/HTML/Elemen/input). Atribut ini juga dapat mengubah rendering visual kawalan bentuk.
|
||||
- `pattern`: memungkinkan untuk menentukan [ungkapan biasa](https://developer.mozilla.org/docs/Web/JavaScript/Guide/Regular_Expressions) untuk menguji sama ada data yang dimasukkan sah atau tidak.
|
||||
|
||||
> Petua: anda boleh menyesuaikan rupa kawalan borang anda bergantung pada apakah itu sah atau tidak menggunakan kelas pseudo CSS `:invalid` dan`:valid`.
|
||||
|
||||
### Tugas
|
||||
|
||||
Terdapat 2 medan yang diperlukan untuk membuat akaun baru yang sah, nama pengguna dan mata wang, medan lain menjadi pilihan. Kemas kini HTML borang, menggunakan atribut `required` dan teks di label medan untuk itu:
|
||||
|
||||
```html
|
||||
<label for="user">Username (required)</label>
|
||||
<input id="user" name="user" type="text" required>
|
||||
...
|
||||
<label for="currency">Currency (required)</label>
|
||||
<input id="currency" name="currency" type="text" value="$" required>
|
||||
```
|
||||
|
||||
Walaupun pelaksanaan pelayan tertentu ini tidak memaksakan had khusus pada panjang maksimum medan, selalu menjadi amalan yang baik untuk menentukan had yang munasabah untuk setiap entri teks pengguna.
|
||||
|
||||
Tambahkan atribut `maxlength` ke medan teks:
|
||||
|
||||
```html
|
||||
<input id="user" name="user" type="text" maxlength="20" required>
|
||||
...
|
||||
<input id="currency" name="currency" type="text" value="$" maxlength="5" required>
|
||||
...
|
||||
<input id="description" name="description" type="text" maxlength="100">
|
||||
```
|
||||
|
||||
Sekarang jika anda menekan butang *Daftar* dan medan tidak mematuhi peraturan pengesahan yang kami tetapkan, anda akan melihat sesuatu seperti ini:
|
||||
|
||||

|
||||
|
||||
Pengesahan seperti ini dilakukan *sebelum* menghantar sebarang data ke pelayan dipanggil pengesahan **client-side**. Tetapi perhatikan bahawa tidak semestinya melakukan semua pemeriksaan tanpa menghantar data. Sebagai contoh, kami tidak dapat memeriksa di sini jika akaun sudah ada dengan nama pengguna yang sama tanpa menghantar permintaan ke pelayan. Pengesahan tambahan yang dilakukan di pelayan dipanggil pengesahan **server-side**.
|
||||
|
||||
Biasanya kedua-duanya perlu dilaksanakan, dan semasa menggunakan pengesahan sisi klien meningkatkan pengalaman pengguna dengan memberikan maklum balas segera kepada pengguna, pengesahan sisi pelayan sangat penting untuk memastikan data pengguna yang anda manipulasi adalah baik dan selamat.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Cabaran
|
||||
|
||||
Tunjukkan mesej ralat dalam HTML jika pengguna sudah ada.
|
||||
|
||||
Berikut adalah contoh bagaimana rupa halaman log masuk akhir setelah sedikit gaya:
|
||||
|
||||

|
||||
|
||||
## Kuiz Pasca Kuliah
|
||||
|
||||
[Kuiz Pasca Kuliah](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/44)
|
||||
|
||||
## Mengkaji & Belajar Sendiri
|
||||
|
||||
Pembangun menjadi sangat kreatif mengenai usaha membina bentuk mereka, terutama mengenai strategi pengesahan. Ketahui mengenai aliran bentuk yang berbeza dengan melihat melalui [CodePen](https://codepen.com); bolehkah anda mencari beberapa bentuk yang menarik dan memberi inspirasi?
|
||||
|
||||
## Tugasan
|
||||
|
||||
[Gayakan bank app anda](assignment.ms.md)
|
@@ -1,297 +0,0 @@
|
||||
# 建立銀行網頁應用程式 Part 2:登入與註冊表單
|
||||
|
||||
## 課前測驗
|
||||
|
||||
[課前測驗](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/43?loc=zh_tw)
|
||||
|
||||
### 大綱
|
||||
|
||||
在大多數當代網頁應用程式中,你可以建立自己的帳戶來擁有自己的私人空間。許多用戶在同一時間可以存取同一個網頁應用程式,你就必須有一套機制分開儲存不同用戶的資料並顯示適當的資訊。我們不會涉及到如何管理[用戶個資的安全](https://zh.wikipedia.org/wiki/%E8%BA%AB%E4%BB%BD%E9%AA%8C%E8%AF%81),它是個相當廣泛的主題,我們僅會確保每個用戶能在這款銀行應用上建立一到多個數位帳戶。
|
||||
|
||||
在這單元中,我們會使用 HTML 表單來新增登入與註冊的功能。我們會看到如何使用伺服器 API 傳遞資料,定義基本的用戶字串輸入之檢查機制。
|
||||
|
||||
### 開始之前
|
||||
|
||||
你需要完成第一單元 [HTML 模板與網頁路由](../../1-template-route/translations/README.zh-tw.md)的應用程式。你還需要安裝 [Node.js](https://nodejs.org) 與在本地端[運行伺服器 API](../../api/translations/README.zh-tw.md) 以傳輸建立帳戶所需的資料。
|
||||
|
||||
你可以測試伺服器是否運作正常,在終端機內輸入指令:
|
||||
|
||||
```sh
|
||||
curl http://localhost:5000/api
|
||||
# -> 會回傳結果 "Bank API v1.0.0"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 表單與其控制
|
||||
|
||||
`<form>` 元素打包了 HTML 文件中使用者輸入與提交資料的地方。有許多種使用者介面(UI)以表單的方式呈現,最常見的內容會包含 `<input>` 與 `<button>` 元素。
|
||||
|
||||
有許多種 `<input>` 的[種類](https://developer.mozilla.org/docs/Web/HTML/Element/input),舉例來說,若要建立使用者輸入使用者名稱的地方,你可以:
|
||||
|
||||
```html
|
||||
<input id="username" name="username" type="text">
|
||||
```
|
||||
|
||||
`name` 屬性同時亦是表單傳輸資料的名稱。`id` 屬性是用來與 `<label>` 做表單控制(form control)的連接。
|
||||
|
||||
> 花點時間看看 [`<input>` 種類](https://developer.mozilla.org/docs/Web/HTML/Element/input)的清單與[其他表單控制](https://developer.mozilla.org/docs/Learn/Forms/Other_form_controls),讓你在建立使用者介面時,有全部供你使用的原生 UI 元素可以參考。
|
||||
|
||||
✅ 紀錄一下 `<input>` 是種[空元素](https://developer.mozilla.org/docs/Glossary/Empty_element),你*不應該*在它後面加上對應的結束標籤。然而,你仍然可以在它的後面使用 `<input/>`,這沒有強制規定。
|
||||
|
||||
表單中的 `<button>` 元素是有些特別。如果你沒有指定它的 `type` 屬性,它會在你輸入文字時,自動地提交表單內容給伺服器。這邊有一些你可以設定的 `type` 內容:
|
||||
|
||||
- `submit`: `<form>` 內的預設型態,按鈕會觸發表單提交這項行為。
|
||||
- `reset`: 按鈕會重置所有表單控制回初始狀態。
|
||||
- `button`: 在按鈕按下時不執行預設行為。你可以藉由 JavaScript 自由定義之後的動作。
|
||||
|
||||
### 課題
|
||||
|
||||
在 `login` 模板內加入表單。我們需要 *使用者名稱(username)* 的輸入框與 *登入(Login)* 的按鈕。
|
||||
|
||||
```html
|
||||
<template id="login">
|
||||
<h1>Bank App</h1>
|
||||
<section>
|
||||
<h2>Login</h2>
|
||||
<form id="loginForm">
|
||||
<label for="username">Username</label>
|
||||
<input id="username" name="user" type="text">
|
||||
<button>Login</button>
|
||||
</form>
|
||||
</section>
|
||||
</template>
|
||||
```
|
||||
|
||||
如果你仔細地看,你會注意到我們在這裡還加了 `<label>` 元素。`<label>` 元素被用來新增文字到 UI 上,譬如說我們的使用者名稱。為了讓表單得以被閱讀,標籤是很重要的,此外它還有額外的優點:
|
||||
|
||||
- 連結標籤到表單控制上,它能幫助使用者的額外工具,好比說螢幕報讀器,理解接下來該提供何種資料。
|
||||
- 你可以點擊標籤,它會跳轉到相對應的輸入框,讓使用觸控型裝置的用戶更容易操作。
|
||||
|
||||
> [網頁親和力](https://developer.mozilla.org/docs/Learn/Accessibility/What_is_accessibility)是非常重要但常被忽視的主題。感謝[語義化 HTML 元素](https://developer.mozilla.org/docs/Learn/Accessibility/HTML)的幫助,建立無障礙的網頁內容變得更加容易。你可以[閱讀更多有關網頁親和力的文章](https://developer.mozilla.org/docs/Web/Accessibility),避免觸犯到常見的錯誤並成為負責任的開發者。
|
||||
|
||||
現在,我們加入第二張表單給用戶註冊使用,就像前一張一樣:
|
||||
|
||||
```html
|
||||
<hr/>
|
||||
<h2>Register</h2>
|
||||
<form id="registerForm">
|
||||
<label for="user">Username</label>
|
||||
<input id="user" name="user" type="text">
|
||||
<label for="currency">Currency</label>
|
||||
<input id="currency" name="currency" type="text" value="$">
|
||||
<label for="description">Description</label>
|
||||
<input id="description" name="description" type="text">
|
||||
<label for="balance">Current balance</label>
|
||||
<input id="balance" name="balance" type="number" value="0">
|
||||
<button>Register</button>
|
||||
</form>
|
||||
```
|
||||
|
||||
藉由 `value` 屬性,我們可以定義輸入框內的預設值。
|
||||
注意一下 `balance` 的輸入類型為 `number`。它看起來與其他輸入框不一樣嗎?試著與它互動看看。
|
||||
|
||||
✅ 你能只利用鍵盤造訪表格,與表格互動嗎?你是如何做到的?
|
||||
|
||||
## 提交資料給伺服器
|
||||
|
||||
現在我們有可以使用的 UI 了,下一個步驟要將資料送給我們的伺服器。讓我們來快速地測試一下程式:在點擊 *Login* 或 *Register* 按鈕後,發生了什麼事?
|
||||
|
||||
你有注意到瀏覽器的網址列改變了嗎?
|
||||
|
||||

|
||||
|
||||
`<form>` 預設的行為:使用 [GET 方法](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3)提交表格,將表格資料接在網址後面,傳送給目前網址的伺服器。然而這個方法有一些缺點:
|
||||
|
||||
- 資料大小有上限限制(大約 2000 字元)
|
||||
- 可以直接在網址內看到資料(對密碼而言,這並不恰當)
|
||||
- 它不能做檔案的上傳
|
||||
|
||||
這也是為什麼你需要將它轉換為 [POST 方法](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5),將表單資料存在 HTTP 請求的內容中。這樣就不會遇到上述的限制。
|
||||
|
||||
> POST 是常見的資料傳輸方法,[在一些特別的情況下](https://www.w3.org/2001/tag/doc/whenToUseGet.html),使用 GET 方法相對起來比較恰當。例如進行搜尋的時候。
|
||||
|
||||
### 課題
|
||||
|
||||
加入 `action` 與 `method` 屬性到註冊表單之中:
|
||||
|
||||
```html
|
||||
<form id="registerForm" action="//localhost:5000/api/accounts" method="POST">
|
||||
```
|
||||
|
||||
現在,試著以你的名字申請新的帳戶。在點擊 *Register* 按鈕後,你應該能看到像這樣的畫面:
|
||||
|
||||

|
||||
|
||||
若所有事情都運作正常,伺服器應該會回應你的請求,附帶 [JSON](https://www.json.org/json-en.html) 包含著你剛建立的帳戶資料。
|
||||
|
||||
✅ 試著以相同名字再註冊一次。發生了什麼事?
|
||||
|
||||
## 不重新載入地提交資料
|
||||
|
||||
你可能會注意到,這些行動間出現了一個小問題:在提交表單時,我們離開了網頁應用,瀏覽器又重新導回到伺服器的網址。我們試著避免網頁應用重新載入所有的頁面,做出[單一頁面應用程式 (SPA)](https://zh.wikipedia.org/zh-tw/%E5%8D%95%E9%A1%B5%E5%BA%94%E7%94%A8)。
|
||||
|
||||
為了讓傳遞資料給伺服器時,不發生頁面重新載入的情況,我們需要使用 JavaScript。
|
||||
|
||||
比起直接在 `<form>` 元素的 `action` 屬性加入網址,你可以使用 `javascript:` 字串接在程式語句前頭來執行自訂的行為。使用這方法也意味著你需要額外修改一些原本瀏覽器會做的行為。
|
||||
|
||||
- 接收表單資料
|
||||
- 轉換並編碼表單資料成合適的格式
|
||||
- 建立 HTTP 請求並傳遞給伺服器
|
||||
|
||||
### 課題
|
||||
|
||||
將註冊表單的 `action` 替換為:
|
||||
|
||||
```html
|
||||
<form id="registerForm" action="javascript:register()">
|
||||
```
|
||||
|
||||
開啟 `app.js`,加入新的函式 `register`:
|
||||
|
||||
```js
|
||||
function register() {
|
||||
const registerForm = document.getElementById('registerForm');
|
||||
const formData = new FormData(registerForm);
|
||||
const data = Object.fromEntries(formData);
|
||||
const jsonData = JSON.stringify(data);
|
||||
}
|
||||
```
|
||||
|
||||
我們使用 `getElementById()` 蒐集表單的元素,使用 [`FormData`](https://developer.mozilla.org/docs/Web/API/FormData) 協助從表單控制中取出 key/value 的數據對。
|
||||
之後,利用 [`Object.fromEntries()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/fromEntries) 轉換資料成正規物件,最後再將檔案轉成 [JSON](https://www.json.org/json-en.html) ── 一個在網路上常見的資料交換格式。
|
||||
|
||||
現在資料已經準備提交給伺服器了。建立新函式 `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' };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
這個函式做了什麼?首先,注意關鍵字 `async`,代表著函式包含了[**非同步化程式**](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/async_function)。在與關鍵字 `await` 一起使用時,它會在繼續運行程式前,等待非同步的程式被執行,就像等待伺服器回應一樣。
|
||||
|
||||
這邊有關於使用 `async/await` 的影片:
|
||||
|
||||
[](https://youtube.com/watch?v=YwmlRkrxvkk "Async 與 Await 管理 promises")
|
||||
|
||||
> 點擊上方圖片以觀看關於 async/await 的影片。
|
||||
|
||||
我們使用 API `fetch()` 來傳送 JSON 資料給伺服器。這個方法需要使用兩個參數:
|
||||
|
||||
- 伺服器的網址,在此使用 `//localhost:5000/api/accounts`。
|
||||
- 網頁請求的設定,就是我們定義 `POST` 方法與提供請求的 `body`。當我們傳輸 JSON 資料給伺服器,我們還需要在標頭的 `Content-Type` 定為 `application/json`,伺服器才知道該如何解讀裡面的內容。
|
||||
|
||||
當伺服器以 JSON 回應請求後,我們可以使用 `await response.json()` 來取得 JSON 的內容並回傳結果。注意在此為非同步程式的方法,我們使用關鍵字 `await` 回傳任何在解讀封包時產生的錯誤訊息。
|
||||
|
||||
現在,在函式 `register` 中呼叫 `createAccount()`:
|
||||
|
||||
```js
|
||||
const result = await createAccount(jsonData);
|
||||
```
|
||||
|
||||
因為我們在這此使用了關鍵字 `await`,我們需要在註冊函式前新增關鍵字 `async`:
|
||||
|
||||
```js
|
||||
async function register() {
|
||||
```
|
||||
|
||||
最後,我們儲存一些紀錄來檢查結果。最後的函式應該會如下方格式:
|
||||
|
||||
```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);
|
||||
}
|
||||
```
|
||||
|
||||
過程有些冗長,但我們達成了!當你開啟[瀏覽器開發者工具](https://developer.mozilla.org/docs/Learn/Common_questions/What_are_browser_developer_tools),試著註冊新的帳戶,你應該能看到網頁並沒有改變,但命令欄中會顯示帳戶成功註冊的訊息。
|
||||
|
||||

|
||||
|
||||
✅ 你覺得傳給伺服器的資料是安全的嗎?其他人有辦法攔截網頁請求嗎?你可以閱讀 [HTTPS](https://en.wikipedia.org/wiki/HTTPS),了解更多關於安全的資料傳輸。
|
||||
|
||||
## 資料驗證
|
||||
|
||||
試著在註冊新帳戶時,不輸入你的使用者名稱,你會發現伺服器回傳了錯誤狀態訊息:[400 (Bad Request)](https://developer.mozilla.org/docs/Web/HTTP/Status/400#:~:text=The%20HyperText%20Transfer%20Protocol%20(HTTP,%2C%20or%20deceptive%20request%20routing).)。
|
||||
|
||||
在傳輸資料給伺服器之前,最好先[驗證表單資料](https://developer.mozilla.org/docs/Learn/Forms/Form_validation),以確保我們傳送合法的網頁請求。 HTML5 表單控制內建包含了驗證方法,使用了多樣的屬性: controls provides built-in validation using various attributes:
|
||||
|
||||
- `required`: 輸入框必須被填寫,否則表單不能被提交。
|
||||
- `minlength` 和 `maxlength`: 定義輸入框的文字下限與文字上限。
|
||||
- `min` 和 `max`: 定義輸入框的數字下限與數字上限。
|
||||
- `type`: 定義輸入框內的資料格式,例如`數字`、`email`、`檔案`或是[其他內建的格式](https://developer.mozilla.org/docs/Web/HTML/Element/input)。這個屬性可能會改變表單控制的表現方法。
|
||||
- `pattern`: 允許定義[正規表示法](https://developer.mozilla.org/docs/Web/JavaScript/Guide/Regular_Expressions)的字串,測試輸入的內容是否滿足它。
|
||||
|
||||
> 提示:你可以自定義表單控制的呈現方法,利用 CSS pseudo-classes `:valid` 和 `:invalid` 判斷內容是否合理。
|
||||
|
||||
### 課題
|
||||
|
||||
在建立新的合法帳戶時,有兩個必須被填寫的輸入框:使用者名稱與資產狀態,而其他選項則是可有可無。現在更新表單的 HTML 語法,使用 `required` 屬性並標記提示在標籤中:
|
||||
|
||||
```html
|
||||
<label for="user">Username (required)</label>
|
||||
<input id="user" name="user" type="text" required>
|
||||
...
|
||||
<label for="currency">Currency (required)</label>
|
||||
<input id="currency" name="currency" type="text" value="$" required>
|
||||
```
|
||||
|
||||
伺服器並沒設定輸入框的文字上限,定義合理的文字輸入上限是必要的。
|
||||
|
||||
在文字框內加入 `maxlength` 屬性:
|
||||
|
||||
```html
|
||||
<input id="user" name="user" type="text" maxlength="20" required>
|
||||
...
|
||||
<input id="currency" name="currency" type="text" value="$" maxlength="5" required>
|
||||
...
|
||||
<input id="description" name="description" type="text" maxlength="100">
|
||||
```
|
||||
|
||||
現在,如果文字框並沒有滿足我們所定義的規則時,在點擊了 *Register* 按鈕後,你會看到:
|
||||
|
||||

|
||||
|
||||
這類在傳輸資料給伺服器*之前*的驗證系統稱之為 **用戶端(client-side)** 驗證。但注意有些資料是沒有辦法在傳輸前被驗證的。舉例來說,我們沒辦法在發出請求前,確認是否已經存在著一組相同姓名的帳戶。伺服器上額外的驗證措施就稱之為 **伺服器端(server-side)** 驗證。
|
||||
|
||||
通常這兩個驗證都需要去編寫,用戶端驗證能及時回饋給用戶,提升使用者體驗;伺服器端驗證確保你要處理的用戶資料是合理且安全的。
|
||||
|
||||
---
|
||||
|
||||
## 🚀 挑戰
|
||||
|
||||
當相同使用者名稱的帳戶已經存在時,在 HTML 上顯示錯誤訊息。
|
||||
|
||||
這邊有做過一些造型的最終登入頁面範本。
|
||||
|
||||

|
||||
|
||||
## 課後測驗
|
||||
|
||||
[課後測驗](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/44?loc=zh_tw)
|
||||
|
||||
## 複習與自學
|
||||
|
||||
開發者在建立表單時需要發揮他們的創意,尤其是策畫資料驗證的規則。在 [CodePen](https://codepen.com) 上學習不同表單流程,你能發現什麼有趣且令人發想的表單嗎?
|
||||
|
||||
## 作業
|
||||
|
||||
[造型化你的銀行程式](assignment.zh-tw.md)
|
@@ -1,13 +0,0 @@
|
||||
# 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. |
|
@@ -1,13 +0,0 @@
|
||||
# Stylisez votre application bancaire
|
||||
|
||||
## Instructions
|
||||
|
||||
Créez un nouveau fichier `styles.css` et ajoutez un lien vers celui-ci dans votre fichier `index.html`. Dans le fichier CSS que vous venez de créer, ajoutez des éléments de style pour que les pages *Login* et *Dashboard* aient un aspect agréable et soigné. Essayez de créer un thème de couleurs pour donner à votre application sa propre image de marque.
|
||||
|
||||
> Conseil : vous pouvez modifier le HTML et ajouter de nouveaux éléments et classes si nécessaire.
|
||||
|
||||
## Rubrique
|
||||
|
||||
| Critères | Exemplaire | Adéquat | Besoin d'amélioration |
|
||||
| -------- | ------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
|
||||
| | Toutes les pages sont propres et lisibles, avec un thème de couleurs cohérent et les différentes sections se détachant correctement. | Les pages sont stylisées mais sans thème ou avec des sections non clairement délimitées. | Les pages manquent de style, les sections sont désorganisées et les informations sont difficiles à lire. |
|
@@ -1,13 +0,0 @@
|
||||
# अपने बैंक ऐप को स्टाइल करें
|
||||
|
||||
## अनुदेश
|
||||
|
||||
एक नई `style.css` फ़ाइल बनाएँ और अपने वर्तमान` index.html` फ़ाइल में एक लिंक जोड़ें. CSS फ़ाइल में आपने सिर्फ _लॉगिन_ और _डैशबोर्ड_ पेज बनाने के लिए कुछ स्टाइलिंग जोड़ दी है जो अच्छी और सुव्यवस्थित दिखती है. अपने ऐप को खुद की ब्रांडिंग देने के लिए कलर थीम बनाने की कोशिश करें.
|
||||
|
||||
> युक्ति: आप HTML को संशोधित कर सकते हैं और यदि आवश्यक हो तो नए तत्व और क्लासेस जोड़ सकते हैं.
|
||||
|
||||
## शीर्ष
|
||||
|
||||
| मानदंड | उदाहरणात्मक | पर्याप्त | सुधार की जरूरत |
|
||||
| ------ | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
|
||||
| | सभी पृष्ठ साफ-सुथरे और पठनीय दिखते हैं, जिसमें एक सुसंगत रंग विषय और विभिन्न खंड ठीक से खड़े हैं. | पृष्ठों को स्टाइल किया गया है, लेकिन बिना थीम के या स्पष्ट रूप से परिसीमित नहीं किए गए अनुभागों के साथ. | पृष्ठों में स्टाइल की कमी है, खंड अव्यवस्थित दिखते हैं और जानकारी को पढ़ना मुश्किल है. |
|
@@ -1,13 +0,0 @@
|
||||
# Dare uno stile alla propria app bancaria
|
||||
|
||||
## Istruzioni
|
||||
|
||||
Creare un nuovo file `styles.css` e aggiungere un collegamento nel file `index.html` corrente. Nel file CSS appena creato aggiungere uno stile per rendere la pagina di *Login* e *cruscotto* bella e ordinata. Provare a creare un tema cromatico per dare alla app il proprio marchio.
|
||||
|
||||
> Suggerimento: si può modificare l'HTML e aggiungere nuovi elementi e classi se necessario.
|
||||
|
||||
## Rubrica
|
||||
|
||||
| Criteri | Ottimo | Adeguato | Necessita miglioramento |
|
||||
| -------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------- |
|
||||
| | Tutte le pagine sembrano pulite e leggibili, con un tema cromatico coerente e le diverse sezioni che risaltano correttamente. | Le pagine hanno uno stile ma senza un tema o con sezioni non chiaramente delimitate. | Le pagine mancano di stile, le sezioni sembrano disorganizzate e le informazioni sono difficili da leggere. |
|
@@ -1,13 +0,0 @@
|
||||
# 銀行アプリのスタイル設定
|
||||
|
||||
## 説明書
|
||||
|
||||
新しい `styles.css` ファイルを作成し、現在の `index.html` ファイルにリンクを追加します。作成した CSS ファイルに、*Login* と *Dashboard* ページがすっきりと整然と見えるように、いくつかのスタイルを追加します。アプリに独自のブランディングを与えるために、カラーテーマを作成してみてください。
|
||||
|
||||
> ヒント: 必要に応じて HTML を修正し、新しい要素やクラスを追加することができます。
|
||||
|
||||
## ルーブリック
|
||||
|
||||
| 基準 | 模範的な例 | 適切な | 改善が必要 |
|
||||
| -------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------- |
|
||||
| | すべてのページは、一貫した色のテーマと異なるセクションが適切に立っていると、きれいで読みやすいように見えます。 | ページはスタイリングされていますが、テーマがなかったり、セクションが明確に区切られていなかったりします。 | ページにスタイルがなく、セクションが乱雑に見え、情報が読みにくい。 |
|
@@ -1,13 +0,0 @@
|
||||
# 은행 앱 스타일링
|
||||
|
||||
## 설명
|
||||
|
||||
새 `styles.css` 파일을 만들고 현재 `index.html` 파일에 링크를 추가해봅니다. 방금 만든 CSS 파일에서 *로그인* 및 *대시 보드* 페이지가 멋지고 깔끔하게 보이도록 스타일을 추가합니다. 앱에 자체 브랜딩을 제공하기 위해 색상 테마를 만들어봅니다.
|
||||
|
||||
> 팁 : HTML을 수정하고 필요한 경우 새 요소와 클래스를 추가 할 수 있습니다.
|
||||
|
||||
## 평가 기준
|
||||
|
||||
기준 | 모범 답안 | 적당한 답안 | 개선이 필요한 답안
|
||||
--- | --- | --- | ---
|
||||
| 모든 페이지가 깔끔하고 읽기 쉬우며 일관된 색상 테마와 다양한 섹션들이 적절하게 돋보이는 경우 | 페이지가 스타일링되어있지만 테마가 없거나 섹션들이 명확하게 구분되지 않은 경우 | 페이지가 스타일링되어 있지 않았으며 섹션들이 질서가 없고 정보를 읽기가 어려운 경우
|
@@ -1,13 +0,0 @@
|
||||
# Gaya aplikasi bank anda
|
||||
|
||||
## Arahan
|
||||
|
||||
Buat fail `styles.css` baru dan tambahkan pautan ke dalam fail `index.html` semasa anda. Dalam fail CSS yang baru anda buat, tambahkan beberapa gaya untuk menjadikan halaman *Login* dan *Dashboard* kelihatan baik dan kemas. Cuba buat tema warna untuk memberikan jenama sendiri pada aplikasi anda.
|
||||
|
||||
> Petua: anda boleh mengubah HTML dan menambahkan elemen dan kelas baru jika diperlukan.
|
||||
|
||||
## Rubrik
|
||||
|
||||
| kriteria | Contoh | Mencukupi | Usaha Lagi |
|
||||
| -------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------- |
|
||||
| | Semua halaman kelihatan bersih dan mudah dibaca, dengan tema warna yang konsisten dan bahagian yang berbeza menonjol dengan betul. | Halaman digayakan tetapi tanpa tema atau bahagian yang tidak dibatasi dengan jelas. | Halaman kurang digayakan, bahagiannya kelihatan tidak teratur dan maklumatnya sukar dibaca. |
|
@@ -1,13 +0,0 @@
|
||||
# Style uw bank-app
|
||||
|
||||
## Instructies
|
||||
|
||||
Maak een nieuw `styles.css`-bestand en voeg er een link naar toe in uw huidige `index.html`-bestand. Voeg in het CSS-bestand dat u zojuist hebt gemaakt wat stijl toe om de pagina *Login* en *Dashboard* er netjes en opgeruimd uit te laten zien. Probeer een kleurenthema te maken om uw app een eigen branding te geven.
|
||||
|
||||
> Tip: u kunt de HTML aanpassen en indien nodig nieuwe elementen en class toevoegen.
|
||||
|
||||
## Rubriek
|
||||
|
||||
| Criteria | Voorbeeldig | Voldoende | Moet worden verbeterd |
|
||||
| -------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------- |
|
||||
| | Alle pagina's zien er schoon en leesbaar uit, met een consistent kleurenthema en de verschillende secties vallen goed op. | Pagina's zijn opgemaakt maar zonder thema of met secties die niet duidelijk zijn afgebakend.| Pagina's missen stijl, de secties zien er ongeorganiseerd uit en de informatie is moeilijk te lezen. |
|
@@ -1,13 +0,0 @@
|
||||
# 造型化你的銀行程式
|
||||
|
||||
## 簡介
|
||||
|
||||
建立新的檔案 `styles.css`,匯入到你的 `index.html` 檔案中。藉由 CSS 檔,你能讓*登入*與*儀表板*頁面看起來更漂亮且整潔。試著為你的程式加入主題色彩,對應到你的品牌。
|
||||
|
||||
> 提示:你可以修改 HTML 檔,在必要時新增元素與 classes。
|
||||
|
||||
## 學習評量
|
||||
|
||||
| 作業內容 | 優良 | 普通 | 待改進 |
|
||||
| -------- | -------------------------------------------------- | ------------------------------------ | ---------------------------------------- |
|
||||
| | 所有的頁面整潔且易讀:統一的主題色彩且排版顯示正常 | 頁面有調整過,但缺乏主題且排版有瑕疵 | 頁面缺乏造型,排版凌亂且頁面資訊難以理解 |
|
@@ -1,59 +0,0 @@
|
||||
# [Tema de la lección]
|
||||
|
||||
! [Insertar un video aquí](video-url)
|
||||
|
||||
## [Prueba previa a la conferencia](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/45)
|
||||
|
||||
[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](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/46)
|
||||
|
||||
## Revisión y autoestudio
|
||||
|
||||
**Vencimiento de la asignación [MM/AA]**: [Nombre de la asignación](assignment.es.md)
|
@@ -1,334 +0,0 @@
|
||||
# Créer une application bancaire Partie 3: Méthodes de récupération et d'utilisation des données
|
||||
|
||||
## Quiz préalable
|
||||
|
||||
[Quiz préalable](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/45?loc=fr)
|
||||
|
||||
### Introduction
|
||||
|
||||
Au cœur de chaque application Web, il y a des *données*. Les données peuvent prendre de nombreuses formes, mais leur objectif principal est toujours d'afficher des informations pour l'utilisateur. Les applications Web devenant de plus en plus interactives et complexes, la manière dont l'utilisateur accède aux informations et interagit avec elles est désormais un élément clé du développement Web.
|
||||
|
||||
Dans cette leçon, nous verrons comment récupérer des données d'un serveur de manière asynchrone et utiliser ces données pour afficher des informations sur une page Web sans recharger le code HTML.
|
||||
|
||||
### Prérequis
|
||||
|
||||
Vous devez avoir créé la partie [Formulaire de connexion et d'inscription](../../2-forms/translations/README.fr.md) de l'application Web pour cette leçon. Vous devez également installer [Node.js](https://nodejs.org) et [exécuter l'API du serveur](../../api/translations/README.fr.md) localement afin d'obtenir les données de compte.
|
||||
|
||||
Vous pouvez tester que le serveur fonctionne correctement en exécutant cette commande dans un terminal:
|
||||
|
||||
```sh
|
||||
curl http://localhost:5000/api
|
||||
# -> devrait renvoyer "Bank API v1.0.0" comme résultat
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## AJAX et la récupération de données
|
||||
|
||||
Les sites Web traditionnels mettent à jour le contenu affiché lorsque l’utilisateur sélectionne un lien ou soumet des données à l’aide d’un formulaire, en rechargeant la page HTML complète. Chaque fois que de nouvelles données doivent être chargées, le serveur Web renvoie une toute nouvelle page HTML qui doit être traitée par le navigateur, interrompant l’action actuelle de l’utilisateur et limitant les interactions pendant le rechargement. Ce flux de travail est également appelé *Application multipage* ou *AMP*.
|
||||
|
||||

|
||||
|
||||
Lorsque les applications Web ont commencé à devenir plus complexes et interactives, une nouvelle technique appelée [AJAX (Asynchronous JavaScript and XML)](https://en.wikipedia.org/wiki/Ajax_(programming)) a émergé. Cette technique permet aux applications Web d’envoyer et de récupérer des données à partir d’un serveur de manière asynchrone à l’aide de JavaScript, sans avoir à recharger la page HTML, ce qui se traduit par des mises à jour plus rapides et des interactions utilisateur plus fluides. Lorsque de nouvelles données sont reçues du serveur, la page HTML actuelle peut également être mise à jour avec JavaScript à l’aide de l’API [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model). Au fil du temps, cette approche a évolué pour devenir ce qu’on appelle maintenant une [*Application d’une seule page* ou *SPA*](https://en.wikipedia.org/wiki/Single-page_application).
|
||||
|
||||

|
||||
|
||||
Lors de l’introduction d’AJAX, la seule API disponible pour récupérer des données de manière asynchrone était [`XMLHttpRequest`](https://developer.mozilla.org/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest). Mais les navigateurs modernes implémentent désormais également [l’API `Fetch`](https://developer.mozilla.org/docs/Web/API/Fetch_API) plus pratique et plus puissante, qui utilise des promesses et est mieux adaptée à la manipulation des données JSON.
|
||||
|
||||
> Bien que tous les navigateurs modernes prennent en charge l’API Fetch, si vous souhaitez que votre application Web fonctionne sur des navigateurs hérités ou anciens, il est toujours judicieux de vérifier d’abord le [tableau de compatibilité sur caniuse.com](https://caniuse.com/fetch).
|
||||
|
||||
### Tâche
|
||||
|
||||
Dans [la leçon précédente](../../2-forms/translations/README.fr.md) nous avons implémenté le formulaire d’inscription pour créer un compte. Nous allons maintenant ajouter du code pour vous connecter à l’aide d’un compte existant et récupérer ses données. Ouvrez le fichier `app.js` et ajoutez une nouvelle fonction `login`:
|
||||
|
||||
```js
|
||||
async function login() {
|
||||
const loginForm = document.getElementById('loginForm')
|
||||
const user = loginForm.user.value;
|
||||
}
|
||||
```
|
||||
|
||||
Ici, nous commençons par récupérer l’élément de formulaire avec `getElementById()`, puis nous obtenons le nom d’utilisateur à partir de l’entrée avec `loginForm.user.value`. Chaque contrôle de formulaire est accessible par son nom (défini dans le code HTML à l’aide de l’attribut `name`) en tant que propriété du formulaire.
|
||||
|
||||
De la même manière que nous avons fait pour l’enregistrement, nous allons créer une autre fonction pour effectuer une demande de serveur, mais cette fois pour récupérer les données du compte:
|
||||
|
||||
```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' };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Nous utilisons l’API `fetch` pour demander les données de manière asynchrone au serveur, mais cette fois, nous n’avons pas besoin de paramètres supplémentaires autres que l’URL à appeler, car nous n’interrogeons que des données. Par défaut, `fetch` crée une requête HTTP [`GET`](https://developer.mozilla.org/docs/Web/HTTP/Methods/GET), ce que nous recherchons ici.
|
||||
|
||||
✅ `encodeURIComponent()` est une fonction qui échappe les caractères spéciaux pour URL. Quels problèmes pourrions-nous avoir si nous n’appelons pas cette fonction et n’utilisons pas directement la valeur `user` dans l’URL?
|
||||
|
||||
Mettons maintenant à jour notre fonction `login` pour utiliser `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');
|
||||
}
|
||||
```
|
||||
|
||||
Tout d’abord, comme `getAccount` est une fonction asynchrone, nous devons la faire correspondre avec le mot-clé `await` pour attendre le résultat du serveur. Comme pour toute demande de serveur, nous devons également traiter les cas d’erreur. Pour l’instant, nous allons seulement ajouter un message de journal pour afficher l’erreur, et y revenir plus tard.
|
||||
|
||||
Ensuite, nous devons stocker les données quelque part afin de pouvoir les utiliser plus tard pour afficher les informations du tableau de bord. Étant donné que la variable `account` n’existe pas encore, nous allons créer une variable globale pour elle en haut de notre fichier:
|
||||
|
||||
```js
|
||||
let account = null;
|
||||
```
|
||||
|
||||
Une fois les données utilisateur enregistrées dans une variable, nous pouvons naviguer de la page *login* au *dashboard* en utilisant la fonction `navigate()` que nous avons déjà.
|
||||
|
||||
Enfin, nous devons appeler notre fonction `login` lorsque le formulaire de login est soumis, en modifiant le HTML:
|
||||
|
||||
```html
|
||||
<form id="loginForm" action="javascript:login()">
|
||||
```
|
||||
|
||||
Vérifiez que tout fonctionne correctement en enregistrant un nouveau compte et en essayant de vous connecter à l’aide du même compte.
|
||||
|
||||
Avant de passer à la partie suivante, nous pouvons également compléter la fonction `register` en ajoutant ceci au bas de la fonction:
|
||||
|
||||
```js
|
||||
account = result;
|
||||
navigate('/dashboard');
|
||||
```
|
||||
|
||||
✅ Saviez-vous que par défaut, vous ne pouvez appeler les API du serveur qu’à partir du *même domaine et port* que la page Web que vous consultez? Il s’agit d’un mécanisme de sécurité appliqué par les navigateurs. Mais attendez, notre application web s’exécute sur 'localhost:3000' alors que l’API du serveur s’exécute sur `localhost:3000`, pourquoi cela fonctionne-t-il? En utilisant une technique appelée [Cross-Origin Resource Sharing (CORS)](https://developer.mozilla.org/docs/Web/HTTP/CORS), il est possible d’effectuer des requêtes HTTP inter-origines si le serveur ajoute des en-têtes spéciaux à la réponse, ce qui permet des exceptions pour des domaines spécifiques.
|
||||
|
||||
> En savoir plus sur les API en suivant cette [leçon](https://docs.microsoft.com/learn/modules/use-apis-discover-museum-art/?WT.mc_id=academic-77807-sagibbon)
|
||||
|
||||
## Mettre à jour le code HTML pour afficher les données
|
||||
|
||||
Maintenant que nous avons les données utilisateur, nous devons mettre à jour le code HTML existant pour l’afficher. Nous savons déjà comment récupérer un élément du DOM en utilisant par exemple `document.getElementById()`. Une fois que vous avez un élément de base, voici quelques API que vous pouvez utiliser pour le modifier ou y ajouter des éléments enfants:
|
||||
|
||||
- En utilisant la propriété [`textContent`](https://developer.mozilla.org/docs/Web/API/Node/textContent), vous pouvez modifier le texte d’un élément. Notez que la modification de cette valeur supprime tous les enfants de l’élément (le cas échéant) et le remplace par le texte fourni. En tant que tel, c’est aussi une méthode efficace pour supprimer tous les enfants d’un élément donné en lui attribuant une chaîne vide `''` à celui-ci.
|
||||
|
||||
- En utilisant [`document.createElement()`](https://developer.mozilla.org/docs/Web/API/Document/createElement) avec la méthode [`append()`](https://developer.mozilla.org/docs/Web/API/ParentNode/append), vous pouvez créer et attacher un ou plusieurs nouveaux éléments enfants.
|
||||
|
||||
✅ En utilisant la propriété [`innerHTML`](https://developer.mozilla.org/docs/Web/API/Element/innerHTML) d’un élément, il est également possible de modifier son contenu HTML, mais celui-ci doit être évité car il est vulnérable aux attaques [cross-site scripting (XSS)](https://developer.mozilla.org/docs/Glossary/Cross-site_scripting).
|
||||
|
||||
### Tâche
|
||||
|
||||
Avant de passer à l’écran du tableau de bord, il y a encore une chose que nous devrions faire sur la page *connexion*. Actuellement, si vous essayez de vous connecter avec un nom d’utilisateur qui n’existe pas, un message s’affiche dans la console, mais pour un utilisateur normal, rien ne change et vous ne savez pas ce qui se passe.
|
||||
|
||||
Ajoutons un élément d’espace réservé dans le formulaire de connexion où nous pouvons afficher un message d’erreur si nécessaire. Un bon endroit serait juste avant la connexion `<button>`:
|
||||
|
||||
```html
|
||||
...
|
||||
<div id="loginError"></div>
|
||||
<button>Login</button>
|
||||
...
|
||||
```
|
||||
|
||||
Cet élément `<div>` est vide, ce qui signifie que rien ne sera affiché à l’écran tant que nous n’y aurons pas ajouté du contenu. Nous lui donnons également un `id` afin que nous puissions le récupérer facilement avec JavaScript.
|
||||
|
||||
Revenez au fichier `app.js` et créez une nouvelle fonction d’assistance `updateElement`:
|
||||
|
||||
```js
|
||||
function updateElement(id, text) {
|
||||
const element = document.getElementById(id);
|
||||
element.textContent = text;
|
||||
}
|
||||
```
|
||||
|
||||
Celui-ci est assez simple: selon un élément *id* et *text*, il mettra à jour le contenu textuel de l’élément DOM avec l'`id` correspondant. Utilisons cette méthode à la place du message d’erreur précédent dans la fonction `login`:
|
||||
|
||||
```js
|
||||
if (data.error) {
|
||||
return updateElement('loginError', data.error);
|
||||
}
|
||||
```
|
||||
|
||||
Maintenant, si vous essayez de vous connecter avec un compte non valide, vous devriez voir quelque chose comme ceci:
|
||||
|
||||

|
||||
|
||||
Maintenant, nous avons un texte d’erreur qui apparaît visuellement, mais si vous l’essayez avec un lecteur d’écran, vous remarquerez que rien n’est annoncé. Pour que le texte ajouté dynamiquement à une page soit annoncé par les lecteurs d’écran, il devra utiliser quelque chose appelé [Live Region](https://developer.mozilla.org/docs/Web/Accessibility/ARIA/ARIA_Live_Regions). Ici, nous allons utiliser un type spécifique de région en direct (live region) appelée alerte:
|
||||
|
||||
```html
|
||||
<div id="loginError" role="alert"></div>
|
||||
```
|
||||
|
||||
Implémentez le même comportement pour les erreurs de la fonction `register` (n’oubliez pas de mettre à jour le code HTML).
|
||||
|
||||
## Afficher les informations sur le tableau de bord
|
||||
|
||||
En utilisant les mêmes techniques que nous venons de voir, nous nous occuperons également d’afficher les informations du compte sur la page du tableau de bord.
|
||||
|
||||
Voici à quoi ressemble un objet de compte reçu du serveur:
|
||||
|
||||
```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 }
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
> Remarque: pour vous faciliter la vie, vous pouvez utiliser le compte `test` préexistant qui est déjà rempli de données.
|
||||
|
||||
### Tâche
|
||||
|
||||
Commençons par remplacer la section "Balance" dans le code HTML pour ajouter des éléments d’espace réservé:
|
||||
|
||||
```html
|
||||
<section>
|
||||
Balance: <span id="balance"></span><span id="currency"></span>
|
||||
</section>
|
||||
```
|
||||
|
||||
Nous ajouterons également une nouvelle section juste en dessous pour afficher la description du compte:
|
||||
|
||||
```html
|
||||
<h2 id="description"></h2>
|
||||
```
|
||||
|
||||
✅ Étant donné que la description du compte fonctionne comme un titre pour le contenu en dessous, elle est marquée sémantiquement comme un en-tête. Apprenez-en davantage sur l’importance de [structure de titre](https://www.nomensa.com/blog/2017/how-structure-headings-web-accessibility) pour l’accessibilité, et jetez un coup d’œil critique à la page pour déterminer ce qui pourrait être un autre titre.
|
||||
|
||||
Ensuite, nous allons créer une nouvelle fonction dans `app.js` pour remplir l’espace réservé:
|
||||
|
||||
```js
|
||||
function updateDashboard() {
|
||||
if (!account) {
|
||||
return navigate('/login');
|
||||
}
|
||||
|
||||
updateElement('description', account.description);
|
||||
updateElement('balance', account.balance.toFixed(2));
|
||||
updateElement('currency', account.currency);
|
||||
}
|
||||
```
|
||||
|
||||
Tout d’abord, nous vérifions que nous avons les données de compte dont nous avons besoin avant d’aller plus loin. Ensuite, nous utilisons la fonction `updateElement()` que nous avons créée précédemment pour mettre à jour le code HTML.
|
||||
|
||||
> Pour rendre l’affichage de la balance plus joli, nous utilisons la méthode [`toFixed(2)`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed) pour forcer l’affichage de la valeur avec 2 chiffres après la virgule.
|
||||
|
||||
Maintenant, nous devons appeler notre fonction `updateDashboard()` chaque fois que le tableau de bord est chargé. Si vous avez déjà terminé le [devoir de la leçon 1](../../1-template-route/translations/README.fr.md) cela devrait être simple, sinon vous pouvez utiliser l’implémentation suivante.
|
||||
|
||||
Ajoutez ce code à la fin de la fonction `updateRoute()`:
|
||||
|
||||
```js
|
||||
if (typeof route.init === 'function') {
|
||||
route.init();
|
||||
}
|
||||
```
|
||||
|
||||
Et mettez à jour la définition des itinéraires avec:
|
||||
|
||||
```js
|
||||
const routes = {
|
||||
'/login': { templateId: 'login' },
|
||||
'/dashboard': { templateId: 'dashboard', init: updateDashboard }
|
||||
};
|
||||
```
|
||||
|
||||
Avec ce changement, chaque fois que la page du tableau de bord est affichée, la fonction `updateDashboard()` est appelée. Après une connexion, vous devriez alors être en mesure de voir le solde du compte, la devise et la description.
|
||||
|
||||
## Créer dynamiquement des lignes de tableau avec des modèles HTML
|
||||
|
||||
Dans la [première leçon](../../1-template-route/translations/README.fr.md) nous avons utilisé des modèles HTML avec la méthode [`appendChild()`](https://developer.mozilla.org/docs/Web/API/Node/appendChild) pour implémenter la navigation dans notre application. Les modèles peuvent également être plus petits et utilisés pour remplir dynamiquement des parties répétitives d’une page.
|
||||
|
||||
Nous utiliserons une approche similaire pour afficher la liste des transactions dans le tableau HTML.
|
||||
|
||||
### Tâche
|
||||
|
||||
Ajoutez un nouveau modèle dans le code HTML `<body>`:
|
||||
|
||||
```html
|
||||
<template id="transaction">
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</template>
|
||||
```
|
||||
|
||||
Ce modèle représente une seule ligne de tableau, avec les 3 colonnes que nous voulons remplir: *date*, *object* et *amount* d’une transaction.
|
||||
|
||||
Ensuite, ajoutez cette propriété `id` à l’élément `<tbody>` du tableau dans le modèle de tableau de bord pour faciliter la recherche à l’aide de JavaScript:
|
||||
|
||||
```html
|
||||
<tbody id="transactions"></tbody>
|
||||
```
|
||||
|
||||
Notre HTML est prêt, passons au code JavaScript et créons une nouvelle fonction `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;
|
||||
}
|
||||
```
|
||||
|
||||
Cette fonction fait exactement ce que ses noms impliquent: en utilisant le modèle que nous avons créé précédemment, elle crée une nouvelle ligne de tableau et remplit son contenu à l’aide de données de transaction. Nous l’utiliserons dans notre fonction `updateDashboard()` pour remplir la table:
|
||||
|
||||
```js
|
||||
const transactionsRows = document.createDocumentFragment();
|
||||
for (const transaction of account.transactions) {
|
||||
const transactionRow = createTransactionRow(transaction);
|
||||
transactionsRows.appendChild(transactionRow);
|
||||
}
|
||||
updateElement('transactions', transactionsRows);
|
||||
```
|
||||
|
||||
Ici, nous utilisons la méthode [`document.createDocumentFragment()`](https://developer.mozilla.org/docs/Web/API/Document/createDocumentFragment) qui crée un nouveau fragment DOM sur lequel nous pouvons travailler, avant de finalement l’attacher à notre tableau HTML.
|
||||
|
||||
Il reste encore une chose à faire avant que ce code puisse fonctionner, car notre fonction `updateElement()` ne prend actuellement en charge que le contenu texte. Changeons un peu son code:
|
||||
|
||||
```js
|
||||
function updateElement(id, textOrNode) {
|
||||
const element = document.getElementById(id);
|
||||
element.textContent = ''; // Removes all children
|
||||
element.append(textOrNode);
|
||||
}
|
||||
```
|
||||
|
||||
Nous utilisons la méthode [`append()`](https://developer.mozilla.org/docs/Web/API/ParentNode/append) car elle permet d’attacher du texte ou des [nœuds DOM](https://developer.mozilla.org/docs/Web/API/Node) à un élément parent, ce qui est parfait pour tous nos cas d’utilisation.
|
||||
|
||||
Si vous essayez d’utiliser le compte `test` pour vous connecter, vous devriez maintenant voir une liste de transactions sur le tableau de bord 🎉.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Challenge
|
||||
|
||||
Travaillez ensemble pour que la page du tableau de bord ressemble à une véritable application bancaire. Si vous avez déjà stylisé votre application, essayez d'utiliser des [requêtes multimédias](https://developer.mozilla.org/docs/Web/CSS/Media_Queries) pour créer un [design réactif](https://developer.mozilla.org/docs/Web/Progressive_web_apps/Responsive/responsive_design_building_blocks) qui fonctionne bien sur les ordinateurs de bureau et les appareils mobiles.
|
||||
|
||||
Voici un exemple de page de tableau de bord stylisée:
|
||||
|
||||

|
||||
|
||||
## Quiz de validation des connaissances
|
||||
|
||||
[Quiz de validation des connaissances](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/46?loc=fr)
|
||||
|
||||
## Affectation
|
||||
|
||||
[Refactorisez et commentez votre code](assignment.fr.md)
|
@@ -1,337 +0,0 @@
|
||||
# एक बैंकिंग ऐप पार्ट 3 का निर्माण करें: डेटा प्राप्त करने और उपयोग करने के तरीके
|
||||
|
||||
## पूर्व व्याख्यान प्रश्नोत्तरी
|
||||
|
||||
[पूर्व व्याख्यान प्रश्नोत्तरी](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/45?loc=hi)
|
||||
|
||||
### परिचय
|
||||
|
||||
हर वेब एप्लिकेशन के मूल में *डेटा* है। डेटा कई रूप ले सकता है, लेकिन इसका मुख्य उद्देश्य हमेशा उपयोगकर्ता को जानकारी प्रदर्शित करना है। वेब एप्लिकेशन तेजी से इंटरेक्टिव और जटिल होने के साथ, उपयोगकर्ता कैसे पहुंचता है और जानकारी के साथ सहभागिता करता है, अब वेब विकास का एक महत्वपूर्ण हिस्सा है।
|
||||
|
||||
इस पाठ में, हम एक सर्वर से डेटा को असिंक्रोनोस रूप से प्राप्त करने का तरीका देखेंगे, और HTML को पुनः लोड किए बिना वेब पेज पर जानकारी प्रदर्शित करने के लिए इस डेटा का उपयोग करेंगे।
|
||||
|
||||
### शर्त
|
||||
|
||||
इस पाठ के लिए आपको वेब ऐप का [लॉगिन और पंजीकरण फॉर्म](../../2-forms/translations/README.hi.md) भाग बनाने की आवश्यकता है। आपको स्थानीय रूप से [Node.js](https://nodejs.org) और [सर्वर एपीआई चलाने](../../api/translations/README.hi.md) स्थापित करने की आवश्यकता है ताकि आपको खाता डेटा प्राप्त हो सके।
|
||||
|
||||
आप परीक्षण कर सकते हैं कि सर्वर टर्मिनल में इस कमांड को निष्पादित करके ठीक से चल रहा है:
|
||||
|
||||
```sh
|
||||
curl http://localhost:5000/api
|
||||
# -> should return "Bank API v1.0.0" as a result
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## AJAX और डेटा लाना
|
||||
|
||||
पारंपरिक वेब साइटें प्रदर्शित सामग्री को अपडेट करती हैं जब उपयोगकर्ता एक लिंक का चयन करता है या पूर्ण HTML पृष्ठ को फिर से लोड करके एक फॉर्म का उपयोग करके डेटा सबमिट करता है। हर बार नए डेटा को लोड करने की आवश्यकता होती है, वेब सर्वर एक नया HTML पृष्ठ लौटाता है जिसे ब्राउज़र द्वारा संसाधित करने की आवश्यकता होती है, वर्तमान उपयोगकर्ता कार्रवाई को बाधित करता है और पुनः लोड के दौरान इंटरैक्शन को सीमित करता है। इस वर्कफ़्लो को *मल्टी-पेज एप्लिकेशन* या *एमपीए* भी कहा जाता है।
|
||||
|
||||

|
||||
|
||||
जब वेब एप्लिकेशन अधिक जटिल और संवादात्मक होने लगे, तो [AJAX (असिंक्रोनोस जावास्क्रिप्ट और XML)](https://en.wikipedia.org/wiki/Ajax_(programming)) नामक एक नई तकनीक सामने आई। यह तकनीक वेब ऐप्स को HTML पेज को फिर से लोड किए बिना, जावास्क्रिप्ट के उपयोग से सर्वर से असिंक्रोनोस रूप से डेटा भेजने और पुनः प्राप्त करने की अनुमति देती है, जिसके परिणामस्वरूप तेज़ अपडेट और सुगम उपयोगकर्ता सहभागिता होती है। जब सर्वर से नया डेटा प्राप्त होता है, तो वर्तमान HTML पृष्ठ को [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Melel) API का उपयोग करके जावास्क्रिप्ट के साथ भी अपडेट किया जा सकता है। समय के साथ, यह दृष्टिकोण अब एक [* सिंगल-पेज एप्लिकेशन* या *एसपीए*](https://en.wikipedia.org/wiki/Single-page_application) कहलाता है।
|
||||
|
||||

|
||||
|
||||
जब AJAX पहली बार पेश किया गया था, तो डेटा को अतुल्य रूप से लाने के लिए उपलब्ध एकमात्र API [`XMLHttpRequest`](https://developer.mozilla.org/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest) था। लेकिन आधुनिक ब्राउज़र अब अधिक सुविधाजनक और शक्तिशाली [`Fetch` API](https://developer.mozilla.org/docs/Web/API/Fetch_API) लागू करते हैं, जो प्रामिसेस का उपयोग करता है और हेरफेर करने के लिए बेहतर अनुकूल है JSON डेटा।
|
||||
|
||||
> जबकि सभी आधुनिक ब्राउज़र `Fetch API` का समर्थन करते हैं, यदि आप चाहते हैं कि आपका वेब एप्लिकेशन विरासत या पुराने ब्राउज़रों पर काम करे, तो यह हमेशा एक अच्छा विचार है कि [caniuse.com पर संगतता तालिका](https://caniuse.com/fetch) पहले की जाँच करें।
|
||||
|
||||
### टास्क
|
||||
|
||||
[पिछले पाठ में](../../2-forms/translations/README.hi.md) हमने खाता बनाने के लिए पंजीकरण फ़ॉर्म लागू किया था। अब हम किसी मौजूदा खाते का उपयोग कर लॉगिन करने के लिए कोड जोड़ेंगे और उसका डेटा प्राप्त करेंगे। `app.js` फ़ाइल खोलें और एक नया `login` फ़ंक्शन जोड़ें:
|
||||
|
||||
```js
|
||||
async function login() {
|
||||
const loginForm = document.getElementById('loginForm')
|
||||
const user = loginForm.user.value;
|
||||
}
|
||||
```
|
||||
|
||||
यहाँ हम `getElementById()` के साथ फॉर्म एलिमेंट को पुनः प्राप्त करके शुरू करते हैं, और फिर हम `loginForm.user.value` के साथ इनपुट से यूज़रनेम प्राप्त करते हैं। प्रत्येक प्रपत्र नियंत्रण को उसके नाम (फॉर्म का गुण के रूप में HTML में `name` विशेषता का उपयोग करके सेट) तक पहुँचा जा सकता है।
|
||||
|
||||
पंजीकरण के लिए हमने जो कुछ किया था, उसी तरह से, हम सर्वर अनुरोध करने के लिए एक और कार्य करेंगे, लेकिन इस बार खाता डेटा प्राप्त करने के लिए:
|
||||
|
||||
```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' };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
हम सर्वर से एसिंक्रोनस रूप से डेटा का अनुरोध करने के लिए `fetch` एपीआई का उपयोग करते हैं, लेकिन इस बार हमें कॉल करने के लिए URL के अलावा किसी भी अतिरिक्त पैरामीटर की आवश्यकता नहीं है, क्योंकि हम केवल डेटा क्वेरी कर रहे हैं। डिफ़ॉल्ट रूप से, 'fetch' एक [`GET`](https://developer.mozilla.org/docs/Web/HTTP/Methods/GET) HTTP अनुरोध बनाता है , जो हम यहाँ चाह रहे हैं।
|
||||
|
||||
✅ `encodeURIComponent()` एक फ़ंक्शन है जो URL के लिए विशेष वर्णों से बच जाता है। यदि हम इस फ़ंक्शन को कॉल नहीं करते हैं और URL में सीधे `user` वैल्यू का उपयोग करते हैं, तो संभवतः हमारे पास क्या समस्याएँ हो सकती हैं?
|
||||
|
||||
आइए अब `getAccount` का उपयोग करने के लिए हमारे `login` फ़ंक्शन को अपडेट करें:
|
||||
|
||||
```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');
|
||||
}
|
||||
```
|
||||
|
||||
सबसे पहले, जैसा कि `getAccount` एक असिंक्रोनोस फ़ंक्शन है, जिसे हमें सर्वर परिणाम की प्रतीक्षा करने के लिए `await` कीवर्ड के साथ मेल खाना चाहिए। किसी भी सर्वर अनुरोध के साथ, हमें त्रुटि मामलों से भी निपटना होगा। अभी के लिए हम केवल त्रुटि प्रदर्शित करने के लिए एक लॉग संदेश जोड़ेंगे, और बाद में वापस आएँगे।
|
||||
|
||||
फिर हमें डेटा को कहीं स्टोर करना होगा ताकि हम बाद में इसे डैशबोर्ड इनफार्मेशन्स को प्रदर्शित कर सकें। चूंकि `account` चर अभी तक मौजूद नहीं है, हम अपनी फ़ाइल के शीर्ष पर इसके लिए एक वैश्विक चर बनाएंगे।
|
||||
|
||||
```js
|
||||
let account = null;
|
||||
```
|
||||
|
||||
उपयोगकर्ता डेटा को एक चर में सहेजे जाने के बाद हम पहले से मौजूद `navigate()` फ़ंक्शन का उपयोग करके *लॉगिन* पृष्ठ से *डैशबोर्ड* तक नेविगेट कर सकते हैं।
|
||||
|
||||
अंत में, हमें HTML को संशोधित करके लॉगिन फ़ॉर्म सबमिट करने पर हमारे `login` फ़ंक्शन को कॉल करने की आवश्यकता है:
|
||||
|
||||
```html
|
||||
<form id="loginForm" action="javascript:login()">
|
||||
```
|
||||
|
||||
परीक्षण करें कि नया खाता पंजीकृत करके और उसी खाते का उपयोग करके लॉगिन करने का प्रयास करके सब कुछ सही ढंग से काम कर रहा है।
|
||||
|
||||
अगले भाग पर जाने से पहले, हम फ़ंक्शन के निचले भाग में इसे जोड़कर `register` फ़ंक्शन को भी पूरा कर सकते हैं:
|
||||
|
||||
```js
|
||||
account = result;
|
||||
navigate('/dashboard');
|
||||
```
|
||||
|
||||
✅ क्या आप जानते हैं कि डिफ़ॉल्ट रूप से, आप सर्वर API को केवल उसी वेब पेज से *उसी डोमेन और पोर्ट* से कॉल कर सकते हैं जो आप देख रहे हैं? यह सुरक्षा तंत्र है जो ब्राउज़र द्वारा लागू किया जाता है। लेकिन रुकिए, हमारा वेब ऐप `localhost:3000` पर चल रहा है जबकि सर्वर एपीआई `localhost:5000` पर चल रहा है, यह काम क्यों नहीं करता है? [क्रॉस-ओरिजिनल रिसोर्स शेयरिंग (CORS)](https://developer.mozilla.org/docs/Web/HTTP/CORS) नामक तकनीक का उपयोग करके, क्रॉस-ऑरिजनल HTTP रिक्वेस्ट करना संभव है अगर सर्वर प्रतिक्रिया के लिए विशेष हेडर जोड़ता है, विशिष्ट डोमेन के लिए अपवाद की अनुमति देता है।
|
||||
|
||||
> इसे [पाठ](https://docs.microsoft.com/learn/modules/use-apis-discover-museum-art/?WT.mc_id=academic-77807-sagibbon) ले कर एपीआई के बारे में और जानें
|
||||
|
||||
## डेटा प्रदर्शित करने के लिए HTML अपडेट करें
|
||||
|
||||
अब जब हमारे पास उपयोगकर्ता डेटा है, तो हमें इसे प्रदर्शित करने के लिए मौजूदा HTML को अपडेट करना होगा। हम पहले से ही जानते हैं कि DOM से एक एलेमेन्ट कैसे प्राप्त किया जा सकता है उदाहरण के लिए `document.getElementById()` का उपयोग करना। आपके पास आधार एलेमेन्ट होने के बाद, यहां कुछ API हैं जिनका उपयोग आप इसे संशोधित करने या इसमें बाल एलेमेन्ट जोड़ने के लिए कर सकते हैं:
|
||||
|
||||
- [`TextContent`](https://developer.mozilla.org/docs/Web/API/Node/textContent) प्रॉपर्टी का उपयोग करके आप किसी एलेमेन्ट का पाठ बदल सकते हैं। ध्यान दें कि इस मान को बदलने से सभी एलेमेन्ट के बच्चे (यदि कोई हो) को हटा देता है और प्रदान किए गए पाठ के साथ बदल देता है। जैसे, यह किसी दिए गए एलेमेन्ट के सभी बच्चों को एक खाली स्ट्रिंग `''` को निर्दिष्ट करके निकालने की एक कुशल विधि है।
|
||||
|
||||
- [`document.createElement()`](https://developer.mozilla.org/docs/Web/API/Document/createElement) के साथ [`append()`](https://developer.mozilla.org/docs/Web/API/ParentNode/append) विधि का उपयोग करके आप बना सकते हैं और एक या अधिक नए बाल एलेमेन्ट संलग्न करें।
|
||||
|
||||
✅ किसी एलेमेन्ट की प्रॉपर्टी का उपयोग करते हुए [`innerHTML`](https://developer.mozilla.org/docs/Web/API/Element/innerHTML) का उपयोग करना संभव है, लेकिन यह एक होना चाहिए [क्रॉस-साइट स्क्रिप्टिंग (XSS)](https://developer.mozilla.org/docs/Glossary/Cross-site_scripting) हमलों के कारण इसकी चपेट में आने से बचा जा सकता है।
|
||||
|
||||
### टास्क
|
||||
|
||||
डैशबोर्ड स्क्रीन पर जाने से पहले, एक और चीज़ है जो हमें *लॉगिन* पेज पर करनी चाहिए। वर्तमान में, यदि आप एक उपयोगकर्ता नाम के साथ लॉगिन करने की कोशिश करते हैं जो मौजूद नहीं है, तो कंसोल में एक संदेश दिखाया जाता है लेकिन एक सामान्य उपयोगकर्ता के लिए कुछ भी नहीं बदलता है और आपको नहीं पता कि क्या चल रहा है।
|
||||
|
||||
आइए लॉगिन फॉर्म में एक प्लेसहोल्डर एलेमेन्ट जोड़ें जहां हम एक त्रुटि संदेश प्रदर्शित कर सकते हैं यदि आवश्यक हो। लॉगिन `<button>` के ठीक पहले एक अच्छी जगह होगी:
|
||||
|
||||
```html
|
||||
...
|
||||
<div id="loginError"></div>
|
||||
<button>Login</button>
|
||||
...
|
||||
```
|
||||
|
||||
यह `<div>` एलेमेन्ट रिक्त है, जिसका अर्थ है कि स्क्रीन पर कुछ भी प्रदर्शित नहीं किया जाएगा जब तक हम इसमें कुछ सामग्री नहीं जोड़ते। हम इसे एक `id` भी देते हैं ताकि हम इसे जावास्क्रिप्ट के साथ आसानी से प्राप्त कर सकें।
|
||||
|
||||
|
||||
`app.js` फ़ाइल पर वापस जाएँ और एक नया सहायक फ़ंक्शन `updateElement` बनाएँ:
|
||||
|
||||
```js
|
||||
function updateElement(id, text) {
|
||||
const element = document.getElementById(id);
|
||||
element.textContent = text;
|
||||
}
|
||||
```
|
||||
|
||||
यह एक बहुत सीधा है: एक एलेमेन्ट *आईडी* और *टेक्स्ट* दिया गया है, यह DOM एलेमेन्ट के टेक्स्ट कंटेंट को `id` के मेल से अपडेट करेगा। आइए `login` फ़ंक्शन में पिछले त्रुटि संदेश के स्थान पर इस मेथड का उपयोग करें:
|
||||
|
||||
```js
|
||||
if (data.error) {
|
||||
return updateElement('loginError', data.error);
|
||||
}
|
||||
```
|
||||
|
||||
अब यदि आप अमान्य खाते से लॉगिन करने का प्रयास करते हैं, तो आपको कुछ इस तरह से देखना चाहिए:
|
||||
|
||||

|
||||
|
||||
अब हमारे पास त्रुटि पाठ है जो नेत्रहीन रूप से दिखाई देता है, लेकिन यदि आप इसे एक स्क्रीन रीडर के साथ आज़माते हैं तो आप देखेंगे कि कुछ भी घोषित नहीं हुआ है। पाठ पाठकों के लिए गतिशील रूप से एक पृष्ठ में जोड़े जाने की घोषणा के लिए, इसे [लाइव रीजन](https://developer.mozilla.org/docs/Web/Accessibility/ARIA/ARIA_Live_Regions) नामक कुछ का उपयोग करने की आवश्यकता होगी। यहां हम एक विशिष्ट प्रकार के लाइव क्षेत्र का उपयोग करने जा रहे हैं, जिसे अलर्ट कहा जाता है:
|
||||
|
||||
```html
|
||||
<div id="loginError" role="alert"></div>
|
||||
```
|
||||
|
||||
`register` फ़ंक्शन त्रुटियों के लिए समान व्यवहार को लागू करें (एचटीएमएल को अपडेट करना न भूलें)।
|
||||
|
||||
## डैशबोर्ड पर जानकारी प्रदर्शित करें
|
||||
|
||||
हमने अभी जो तकनीक देखी है, उसी का उपयोग करते हुए, हम डैशबोर्ड पृष्ठ पर खाता जानकारी प्रदर्शित करने का भी ध्यान रखेंगे।
|
||||
|
||||
सर्वर से प्राप्त खाता ऑब्जेक्ट ऐसा दिखता है:
|
||||
|
||||
```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 }
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
> नोट: अपने जीवन को आसान बनाने के लिए, आप पहले से मौजूद `test` खाते का उपयोग कर सकते हैं जो पहले से ही डेटा से आबाद है।
|
||||
|
||||
### टास्क
|
||||
|
||||
प्लेसहोल्डर एलिमेंटस को जोड़ने के लिए HTML में "बैलेंस" अनुभाग को प्रतिस्थापित करके शुरू करें:
|
||||
|
||||
```html
|
||||
<section>
|
||||
Balance: <span id="balance"></span><span id="currency"></span>
|
||||
</section>
|
||||
```
|
||||
|
||||
|
||||
खाता विवरण प्रदर्शित करने के लिए हम नीचे एक नया अनुभाग भी जोड़ेंगे:
|
||||
|
||||
```html
|
||||
<h2 id="description"></h2>
|
||||
```
|
||||
|
||||
✅ चूंकि खाता विवरण इसके नीचे की सामग्री के लिए एक शीर्षक के रूप में कार्य करता है, इसलिए इसे एक शीर्षक के रूप में शब्दार्थ के रूप में चिह्नित किया जाता है। पहुँच क्षमता के लिए [शीर्षक संरचना](https://www.nomensa.com/blog/2017/how-structure-headings-web-accessibility) के बारे में और जानें, और क्या निर्धारित करने के लिए पृष्ठ पर एक महत्वपूर्ण नज़र डालें एक शीर्षक हो सकता है।
|
||||
|
||||
अगला, हम प्लेसहोल्डर में भरने के लिए `app.js` में एक नया फ़ंक्शन बनाएंगे:
|
||||
|
||||
```js
|
||||
function updateDashboard() {
|
||||
if (!account) {
|
||||
return navigate('/login');
|
||||
}
|
||||
|
||||
updateElement('description', account.description);
|
||||
updateElement('balance', account.balance.toFixed(2));
|
||||
updateElement('currency', account.currency);
|
||||
}
|
||||
```
|
||||
|
||||
पहले, हम जाँचते हैं कि आगे जाने से पहले हमारे पास खाता डेटा है जो हमें चाहिए। तब हम HTML को अपडेट करने के लिए पहले बनाए गए `updateElement()` फ़ंक्शन का उपयोग करते हैं।
|
||||
|
||||
> बैलेंस डिस्प्ले को प्रीटियर करने के लिए, हम दशमलव बिंदु के बाद 2 अंकों के साथ मान प्रदर्शित करने के लिए मजबूर करने के लिए [`toFixed (2)`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed) विधि का उपयोग करते हैं।
|
||||
|
||||
अब हमें अपने `updateDashboard()` को हर बार डैशबोर्ड लोड होने पर कॉल करने की आवश्यकता है। यदि आपने पहले ही [पाठ 1 असाइनमेंट](../../1-template-route/translations/assignment.hi.md) को पूरा कर लिया है, तो यह स्ट्रेचेफोवर्ड होना चाहिए, अन्यथा आप निम्नलिखित कार्यान्वयन का उपयोग कर सकते हैं।
|
||||
|
||||
इस कोड को `updateRoute()` फ़ंक्शन के अंत में जोड़ें:
|
||||
|
||||
```js
|
||||
if (typeof route.init === 'function') {
|
||||
route.init();
|
||||
}
|
||||
```
|
||||
|
||||
और मार्गों की परिभाषा के साथ अद्यतन करें:
|
||||
|
||||
```js
|
||||
const routes = {
|
||||
'/login': { templateId: 'login' },
|
||||
'/dashboard': { templateId: 'dashboard', init: updateDashboard }
|
||||
};
|
||||
```
|
||||
|
||||
इस परिवर्तन के साथ, हर बार डैशबोर्ड पृष्ठ प्रदर्शित होने पर, फ़ंक्शन `updateDashboard()` कल किया जाता है। एक लॉगिन के बाद, आपको तब खाता शेष, मुद्रा और विवरण देखने में सक्षम होना चाहिए।
|
||||
|
||||
## HTML टेम्पलेट के साथ गतिशील रूप से तालिका पंक्तियाँ बनाएं
|
||||
|
||||
[पहला पाठ](../../1-template-route/translations/README.hi.md) मे हमने एचटीएमएल टेम्पलेटके साथ [`appendChild()`](https://developer.mozilla.org/docs/Web/API/Node/appendChild) मेथड को नेवीगेशनके लिए अपने एप मे लागू किया था । टेम्प्लेट छोटे भी हो सकते हैं और किसी पृष्ठ के दोहराए गए भागों को गतिशील रूप से आबाद करने के लिए उपयोग किए जा सकते हैं।
|
||||
|
||||
हम एचटीएमएल तालिका में लेनदेन की सूची प्रदर्शित करने के लिए एक समान दृष्टिकोण का उपयोग करेंगे।
|
||||
|
||||
### टास्क
|
||||
|
||||
HTML `<body>` में एक नया टेम्प्लेट जोड़ें :
|
||||
|
||||
```html
|
||||
<template id="transaction">
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</template>
|
||||
```
|
||||
|
||||
यह टेम्प्लेट एक एकल तालिका पंक्ति का प्रतिनिधित्व करता है, 3 कॉलम जिन्हें हम आबाद करना चाहते हैं: *डेट*, *ऑब्जेक्ट* और लेनदेन की *अमाउन्ट*।
|
||||
|
||||
फिर, इस `<id>` गुण को डैशबोर्ड टेम्पलेट के भीतर स्थित तालिका के तत्व `<tbody>` में जोड़ें, जिससे जावास्क्रिप्ट का उपयोग करना आसान हो:
|
||||
|
||||
```html
|
||||
<tbody id="transactions"></tbody>
|
||||
```
|
||||
|
||||
हमारा HTML तैयार है, चलो जावास्क्रिप्ट कोड पर जाएँ और एक नया फ़ंक्शन `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;
|
||||
}
|
||||
```
|
||||
|
||||
यह फ़ंक्शन ठीक वही करता है जो इसके नाम का अर्थ है: हमने पहले बनाए गए टेम्पलेट का उपयोग करके, यह एक नई तालिका पंक्ति बनाता है और लेनदेन डेटा का उपयोग करके अपनी सामग्री में भरता है। हम टेबल को आबाद करने के लिए अपने `updateDashboard()` फ़ंक्शन में इसका उपयोग करेंगे:
|
||||
|
||||
```js
|
||||
const transactionsRows = document.createDocumentFragment();
|
||||
for (const transaction of account.transactions) {
|
||||
const transactionRow = createTransactionRow(transaction);
|
||||
transactionsRows.appendChild(transactionRow);
|
||||
}
|
||||
updateElement('transactions', transactionsRows);
|
||||
```
|
||||
|
||||
यहां हम उस मेथड का उपयोग करते हैं [`document.createDocumentFragment()`](https://developer.mozilla.org/docs/Web/API/Document/createDocumentFragment) जो एक नया DOM टुकड़ा बनाता है जिस पर हम काम कर सकते हैं, अंत में इसे हमारे HTML तालिका में संलग्न करने से पहले।
|
||||
|
||||
इस कोड के काम करने से पहले अभी भी हमें एक और काम करना है, क्योंकि हमारा `updateElement()` फ़ंक्शन वर्तमान में केवल टेक्स्ट सामग्री का सपोर्ट करता है। आइए इसके कोड को थोड़ा बदलें:
|
||||
|
||||
```js
|
||||
function updateElement(id, textOrNode) {
|
||||
const element = document.getElementById(id);
|
||||
element.textContent = ''; // Removes all children
|
||||
element.append(textOrNode);
|
||||
}
|
||||
```
|
||||
|
||||
हम [`append()`](https://developer.mozilla.org/docs/Web/API/ParentNode/append) मेथड का उपयोग करते हैं क्योंकि यह टेक्स्ट या [DOM नोड्स](https://developer.mozilla.org/docs/Web/API/Node) को पेरन्ट एलेमेन्टसे जोड़ने की अनुमति देता है , जो हमारे सभी उपयोग मामलों के लिए एकदम सही है।
|
||||
|
||||
यदि आप लॉगिन करने के लिए `टेस्ट` खाते का उपयोग करने का प्रयास करते हैं, तो आपको अब डैशबोर्ड पर एक लेनदेन सूची देखनी चाहिए 🎉.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 चुनौती
|
||||
|
||||
डैशबोर्ड पृष्ठ को वास्तविक बैंकिंग ऐप की तरह बनाने के लिए एक साथ काम करें। यदि आप पहले से ही अपने ऐप को स्टाइल करते हैं, तो डेस्कटॉप और मोबाइल डिवाइस दोनों पर अच्छी तरह से काम करते हुए [उत्तरदायी डिज़ाइन](https://developer.mozilla.org/docs/Web/Progressive_web_apps/Responsive/responsive_design_building_blocks) बनाने के लिए [मीडिया क्वेरीज़](https://developer.mozilla.org/docs/Web/CSS/Media_Queries) का उपयोग करने का प्रयास करें।
|
||||
|
||||
यहाँ एक सत्यलेड डैशबोर्ड पृष्ठ का उदाहरण दिया गया है:
|
||||
|
||||

|
||||
|
||||
## व्याख्यान उपरांत प्रश्नोत्तरी
|
||||
|
||||
[व्याख्यान उपरांत प्रश्नोत्तरी](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/46?loc=hi)
|
||||
|
||||
## असाइनमेंट
|
||||
|
||||
[Refactor and comment your code](assignment.hi.md)
|
@@ -1,334 +0,0 @@
|
||||
# Creazione di un'App Bancaria Parte 3: Metodi di Recupero e Utilizzo Dati
|
||||
|
||||
## Quiz Pre-Lezione
|
||||
|
||||
[Quiz Pre-Lezione](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/45?loc=it)
|
||||
|
||||
### Introduzione
|
||||
|
||||
Al centro di ogni applicazione web ci sono i *dati*. I dati possono assumere molte forme, ma il loro scopo principale è sempre quello di mostrare le informazioni all'utente. Con le app web che diventano sempre più interattive e complesse, il modo in cui l'utente accede e interagisce con le informazioni è ora una parte fondamentale dello sviluppo web.
|
||||
|
||||
In questa lezione si vedrà come recuperare i dati da un server in modo asincrono e utilizzare questi dati per visualizzare le informazioni su una pagina web senza ricaricare l'HTML.
|
||||
|
||||
### Prerequisito
|
||||
|
||||
Per questa lezinoe è necessario aver creato la parte [Form di Accesso e Registrazione](../../2-forms/translations/README.it.md) dell'app web. È inoltre necessario installare [Node.js](https://nodejs.org) ed eseguire [l'API del server](../../api/translations/README.it.md) in locale in modo da ottenere i dati dell'account.
|
||||
|
||||
Si può verificare che il server funzioni correttamente eseguendo questo comando in un terminale:
|
||||
|
||||
```sh
|
||||
curl http://localhost:5000/api
|
||||
# -> dovrebbe restituire "Bank API v1.0.0" come risultato
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## AJAX e recupero dati
|
||||
|
||||
I siti Web tradizionali aggiornano il contenuto visualizzato quando l'utente seleziona un collegamento o invia dati utilizzando un form, ricaricando l'intera pagina HTML. Ogni volta che è necessario caricare nuovi dati, il server web restituisce una nuova pagina HTML che deve essere elaborata dal browser, interrompendo l'azione corrente dell'utente e limitando le interazioni durante il ricaricamento. Questo flusso di lavoro è anche chiamato *Applicazione MultiPagina* o *MPA* (Multi-Page Application) .
|
||||
|
||||

|
||||
|
||||
Quando le applicazioni web hanno iniziato a diventare più complesse e interattive, è emersa una nuova tecnica chiamata [AJAX (Asynchronous JavaScript and XML)](https://it.wikipedia.org/wiki/AJAX)). Questa tecnica consente alle app web di inviare e recuperare dati da un server in modo asincrono utilizzando JavaScript, senza dover ricaricare la pagina HTML, con conseguenti aggiornamenti più rapidi e interazioni utente più fluide. Quando vengono ricevuti nuovi dati dal server, la pagina HTML corrente può anche essere aggiornata con JavaScript utilizzando l'API [DOM](https://developer.mozilla.org/it/docs/Web/API/Document_Object_Model). Nel tempo, questo approccio si è evoluto in quella che ora viene chiamata [*Applicazione a Pagina Singola* o *SPA*](https://it.wikipedia.org/wiki/Single-page_application) (Single-Page Application).
|
||||
|
||||

|
||||
|
||||
Quando è stato introdotto per la prima volta AJAX, l'unica API disponibile per recuperare i dati in modo asincrono era [`XMLHttpRequest`](https://developer.mozilla.org/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest). I browser moderni ora implementano anche l'[API Fetch](https://developer.mozilla.org/it/docs/Web/API/Fetch_API), più conveniente e potente, che utilizza le promesse ed è più adatta per manipolare i dati JSON.
|
||||
|
||||
> Sebbene tutti i browser moderni supportino l'`API Fetch`, se si desidera che la propria applicazione web funzioni su browser legacy o vecchi, è sempre una buona idea controllare prima la [tabella di compatibilità su caniuse.com](https://caniuse.com/fetch).
|
||||
|
||||
### Attività
|
||||
|
||||
[Nella lezione precedente](../../2-forms/translations/README.it.md) si è implementato il form di registrazione per creare un account. Ora verrà aggiunto il codice per accedere utilizzando un account esistente e recuperarne i dati. Aprire il file `app.js` e aggiungere una nuova funzione di accesso (`login`):
|
||||
|
||||
```js
|
||||
async function login() {
|
||||
const loginForm = document.getElementById('loginForm')
|
||||
const user = loginForm.user.value;
|
||||
}
|
||||
```
|
||||
|
||||
Qui si inizia recuperando l'elemento form con `getElementById()`, quindi si ottiene il nome utente (user) dall'input con `loginForm.user.value`. È possibile accedere a ogni controllo del form tramite il suo nome (impostato nell'HTML utilizzando l'attributo `name`) come proprietà del form.
|
||||
|
||||
In modo simile a quanto fatto per la registrazione, verrà creata un'altra funzione per eseguire una richiesta al server, ma questa volta per recuperare i dati dell'account:
|
||||
|
||||
```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' };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Si usa l'API `fetch` per richiedere i dati in modo asincrono dal server, ma questa volta non servono parametri aggiuntivi oltre all'URL da chiamare, poiché si sta solo interrogando i dati. Per impostazione predefinita, `fetch` crea una richiesta HTTP [`GET`](https://developer.mozilla.org/docs/Web/HTTP/Methods/GET), che è ciò che serve qui.
|
||||
|
||||
✅ `encodeURIComponent()` è una funzione che evita i caratteri speciali per l'URL. Quali problemi si potrebbero avere se non viene chiamata questa funzione e si utilizza direttamente il valore del campo `user` nell'URL?
|
||||
|
||||
Aggiornare ora la funzione `login` per utilizzare `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');
|
||||
}
|
||||
```
|
||||
|
||||
Innanzitutto, poiché `getAccount` è una funzione asincrona, occorre associarla alla parola chiave `await` per attendere il risultato dal server. Come con qualsiasi richiesta al server, occorre anche gestire i casi di errore. Per ora si aggiungerà solo un messaggio di log per visualizzare l'errore e verrà indirizzato più tardi.
|
||||
|
||||
Quindi si deve memorizzare i dati da qualche parte in modo da poterli utilizzare in seguito per visualizzare le informazioni nel cruscotto. Poiché la variabile `account` non esiste ancora, verrà creata come variabile globale all'inizio del file:
|
||||
|
||||
```js
|
||||
let account = null;
|
||||
```
|
||||
|
||||
Dopo che i dati dell'utente sono stati salvati in una variabile, si può navigare dalla pagina di accessi (*login*) al cruscotto (*dashboard*) utilizzando la funzione `navigate()` che già esiste.
|
||||
|
||||
Infine, si deve chiamare la funzione `login` quando viene inviato il form di accesso, modificando l'HTML:
|
||||
|
||||
```html
|
||||
<form id="loginForm" action="javascript:login()">
|
||||
```
|
||||
|
||||
Verificare che tutto funzioni correttamente registrando un nuovo account e provando ad accedere utilizzando lo stesso account.
|
||||
|
||||
Prima di passare alla parte successiva, si può anche completare la funzione di registrazione (`register`) aggiungendola in fondo alla funzione:
|
||||
|
||||
```js
|
||||
account = result;
|
||||
navigate('/dashboard');
|
||||
```
|
||||
|
||||
✅ Si sa che per impostazione predefinita, si possono chiamare solo le API di un server che ha *stesso dominio e porta* della pagina web che si sta visualizzando? Questo è un meccanismo di sicurezza applicato dai browser. Un momento, la nostra app web è in esecuzione su `localhost:3000` mentre l'API del server è in esecuzione su `localhost:5000`, come mai funziona? Utilizzando una tecnica chiamata CORS [Cross-Origin Resource Sharing](https://developer.mozilla.org/it/docs/Web/HTTP/CORS), è possibile eseguire richieste HTTP con diverse origini (cross-origin) se il server aggiunge intestazioni speciali alla risposta, consentendo eccezioni per domini specifici.
|
||||
|
||||
> Ulteriori informazioni sulle API sono disponibili seguendo questa [lezione](https://docs.microsoft.com/learn/modules/use-apis-discover-museum-art?WT.mc_id=academic-4621-cxa)
|
||||
|
||||
## Aggiornare HTML per visualizzare i dati
|
||||
|
||||
Ora che si hanno i dati dell'utente, occorre aggiornare l'HTML esistente per visualizzarli. E' già noto come recuperare un elemento dal DOM utilizzando ad esempio `document.getElementById()`. Dopo aver ottenuto un elemento base, ecco alcune API che si possono utilizzare per modificarlo o aggiungervi elementi figlio:
|
||||
|
||||
- Usando la proprietà [`textContent`](https://developer.mozilla.org/docs/Web/API/Node/textContent) si può cambiare il testo di un elemento. Notare che la modifica di questo valore rimuove tutti i figli dell'elemento (se presenti) e li sostituisce con il testo fornito. In quanto tale, è anche un metodo efficiente per rimuovere tutti i figli di un dato elemento assegnandogli una stringa vuota `""` .
|
||||
|
||||
- Usando [`document.createElement()`](https://developer.mozilla.org/docs/Web/API/Document/createElement) insieme al metodo [`append()`](https://developer.mozilla.org/docs/Web/API/ParentNode/append) si può creare e allegare uno o più nuovi elementi figlio.
|
||||
|
||||
✅ Utilizzando la proprietà [`innerHTML`](https://developer.mozilla.org/docs/Web/API/Element/innerHTML) di un elemento è anche possibile modificare il suo contenuto HTML, ma questo dovrebbe essere evitato poiché è vulnerabile agli attacchi di [cross-site scripting (XSS)](https://developer.mozilla.org/docs/Glossary/Cross-site_scripting) .
|
||||
|
||||
### Attività
|
||||
|
||||
Prima di passare alla schermata del cruscotto, c'è ancora una cosa da fare nella pagina di accesso (*login*). Attualmente, se si prova ad accedere con un nome utente che non esiste, viene mostrato un messaggio nella console ma per un utente normale non cambia nulla e non sa cosa sta succedendo.
|
||||
|
||||
Si aggiunge ora un elemento segnaposto nel form di accesso dove si può visualizzare un messaggio di errore se necessario. Un buon posto sarebbe subito prima del pulsante (`<button>`) con testo Login:
|
||||
|
||||
```html
|
||||
...
|
||||
<div id="loginError"></div>
|
||||
<button>Login</button>
|
||||
...
|
||||
```
|
||||
|
||||
Questo elemento `<div>` è vuoto, il che significa che non verrà visualizzato nulla sullo schermo fino a quando non verrà aggiunto del contenuto. Occorre anche assegnarli un `ID` in modo da poterlo recuperare facilmente con JavaScript.
|
||||
|
||||
Tornare al file `app.js` e creare una nuova funzione di supporto `updateElement`:
|
||||
|
||||
```js
|
||||
function updateElement(id, text) {
|
||||
const element = document.getElementById(id);
|
||||
element.textContent = text;
|
||||
}
|
||||
```
|
||||
|
||||
Questo è abbastanza semplice: dato un *ID* di un elemento e un *testo*, si aggiornerà il contenuto del testo dell'elemento DOM con l'`id` corrispondente. Si usa questo metodo al posto del precedente messaggio di errore nella funzione `login` :
|
||||
|
||||
```js
|
||||
if (data.error) {
|
||||
return updateElement('loginError', data.error);
|
||||
}
|
||||
```
|
||||
|
||||
Ora se si prova ad accedere con un account non valido, si dovrebbe vedere qualcosa del genere:
|
||||
|
||||

|
||||
|
||||
Ora si ha un testo di errore che viene visualizzato, ma se si prova con un lettore di schermo si noterà che non viene annunciato nulla. Affinché il testo che viene aggiunto dinamicamente a una pagina venga annunciato dai lettori di schermo, sarà necessario utilizzare qualcosa chiamato [Live Region](https://developer.mozilla.org/docs/Web/Accessibility/ARIA/ARIA_Live_Regions). Qui verrà utilizzato un tipo specifico di live region chiamato alert:
|
||||
|
||||
```html
|
||||
<div id="loginError" role="alert"></div>
|
||||
```
|
||||
|
||||
Implementare lo stesso comportamento per gli errori della funzione `register` (non dimenticare di aggiornare l'HTML).
|
||||
|
||||
## Visualizzare le informazioni sulla dashboard
|
||||
|
||||
Utilizzando le stesse tecniche appena viste, ci si occuperà anche di visualizzare le informazioni dell'account nella pagina del cruscotto.
|
||||
|
||||
Ecco come appare un oggetto account ricevuto dal server:
|
||||
|
||||
```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 }
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
> Nota: per semplificare, si può utilizzare l'account `test` preesistente già popolato di dati.
|
||||
|
||||
### Attività
|
||||
|
||||
Iniziare sostituendo la sezione "Balance" (saldo) nell'HTML per aggiungere elementi segnaposto:
|
||||
|
||||
```html
|
||||
<section>
|
||||
Balance: <span id="balance"></span><span id="currency"></span>
|
||||
</section>
|
||||
```
|
||||
|
||||
Si aggiungerà anche una nuova sezione appena sotto per visualizzare la descrizione dell'account:
|
||||
|
||||
```html
|
||||
<h2 id="description"></h2>
|
||||
```
|
||||
|
||||
✅ Poiché la descrizione del'account funge da titolo per il contenuto sottostante, viene contrassegnata semanticamente come intestazione (heading). Scoprire di più su come [la struttura delle intestazioni](https://www.nomensa.com/blog/2017/how-structure-headings-web-accessibility) è importante per l'accessibilità ed esaminare la pagina per determinare cos'altro potrebbe essere un'intestazione.
|
||||
|
||||
Successivamente, verrà creata una nuova funzione in `app.js` per riempire il segnaposto:
|
||||
|
||||
```js
|
||||
function updateDashboard() {
|
||||
if (!account) {
|
||||
return navigate('/login');
|
||||
}
|
||||
|
||||
updateElement('description', account.description);
|
||||
updateElement('balance', account.balance.toFixed(2));
|
||||
updateElement('currency', account.currency);
|
||||
}
|
||||
```
|
||||
|
||||
Innanzitutto, controllare di avere i dati dell'account necessari prima di andare oltre. Quindi si usa la funzione `updateElement()` creata in precedenza per aggiornare l'HTML.
|
||||
|
||||
> Per rendere la visualizzazione del saldo più bella, si usa il metodo [`toFixed(2)`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed) per forzare la visualizzazione del valore con 2 cifre per la parte decimale.
|
||||
|
||||
Ora occorre chiamare la funzione `updateDashboard()` ogni volta che viene caricato il cruscotto. Se si è già terminato il [compito della lezione 1](../../1-template-route/translations/assignment.it.md) , questo dovrebbe essere immediato, altrimenti si può utilizzare la seguente implementazione.
|
||||
|
||||
Aggiungere questo codice alla fine della funzione `updateRoute()`:
|
||||
|
||||
```js
|
||||
if (typeof route.init === 'function') {
|
||||
route.init();
|
||||
}
|
||||
```
|
||||
|
||||
Aggiornare la definizione delle rotte con:
|
||||
|
||||
```js
|
||||
const routes = {
|
||||
'/login': { templateId: 'login' },
|
||||
'/dashboard': { templateId: 'dashboard', init: updateDashboard }
|
||||
};
|
||||
```
|
||||
|
||||
Con questa modifica, ogni volta che viene visualizzata la pagina del cruscotto viene chiamata la funzione `updateDashboard()`. Dopo un accesso, si dovrebbe essere in grado di vedere il saldo del conto, la valuta e la descrizione.
|
||||
|
||||
## Creare righe di tabelle dinamicamente con modelli HTML
|
||||
|
||||
Nella [prima lezione](../../1-template-route/translations/README.it.md) sono stati utilizzati modelli HTML insieme al metodo [`appendChild()`](https://developer.mozilla.org/docs/Web/API/Node/appendChild) per implementare la navigazione nell'app. I modelli possono anche essere più piccoli e utilizzati per popolare dinamicamente parti ripetitive di una pagina.
|
||||
|
||||
Verrà usato un approccio simile per visualizzare l'elenco delle transazioni nella tabella HTML.
|
||||
|
||||
### Attività
|
||||
|
||||
Aggiungere un nuovo modello nell'elemento HTML `<body>`:
|
||||
|
||||
```html
|
||||
<template id="transaction">
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</template>
|
||||
```
|
||||
|
||||
Questo modello rappresenta una singola riga di tabella, con le 3 colonne che si vuole popolare: *data*, *oggetto* e *importo* di una transazione.
|
||||
|
||||
Aggiungere quindi la proprietà `id` all'elemento `<tbody>` della tabella all'interno del modello del cruscotto per semplificare la ricerca dell'elemento utilizzando JavaScript:
|
||||
|
||||
```html
|
||||
<tbody id="transactions"></tbody>
|
||||
```
|
||||
|
||||
L'HTML è pronto, ora si passa al codice JavaScript e si crea una nuova funzione `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;
|
||||
}
|
||||
```
|
||||
|
||||
Questa funzione fa esattamente ciò che implica il suo nome: utilizzando il modello creato in precedenza, crea una nuova riga di tabella e riempie il suo contenuto utilizzando i dati della transazione. Verrà usata nella funzione `updateDashboard()` per popolare la tabella:
|
||||
|
||||
```js
|
||||
const transactionsRows = document.createDocumentFragment();
|
||||
for (const transaction of account.transactions) {
|
||||
const transactionRow = createTransactionRow(transaction);
|
||||
transactionsRows.appendChild(transactionRow);
|
||||
}
|
||||
updateElement('transactions', transactionsRows);
|
||||
```
|
||||
|
||||
Qui si utilizza il metodo [`document.createDocumentFragment()`](https://developer.mozilla.org/docs/Web/API/Document/createDocumentFragment) che crea un nuovo frammento DOM su cui si può lavorare, prima di collegarlo finalmente alla tabella HTML.
|
||||
|
||||
C'è ancora un'altra cosa da fare prima che questo codice possa funzionare, poiché la funzione `updateElement()` attualmente supporta solo contenuto di testo. Occorre cambiare un poco il suo codice:
|
||||
|
||||
```js
|
||||
function updateElement(id, textOrNode) {
|
||||
const element = document.getElementById(id);
|
||||
element.textContent = ''; // Rimuove tutti i figli
|
||||
element.append(textOrNode);
|
||||
}
|
||||
```
|
||||
|
||||
Si usa il metodo [`append()`](https://developer.mozilla.org/docs/Web/API/ParentNode/append) in quanto consente di allegare testo o [nodi DOM](https://developer.mozilla.org/docs/Web/API/Node) a un elemento genitore, che è perfetto per tutti i casi d'uso.
|
||||
|
||||
Se si prova a utilizzare l'account `test` per accedere, ora si dovrebbe vedere un elenco di transazioni sul cruscotto 🎉.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Sfida
|
||||
|
||||
Collaborare per far sembrare la pagina del cruscotto una vera app bancaria. Se è già stato definito lo stile della propria app, provare a utilizzare le [media query](https://developer.mozilla.org/docs/Web/CSS/Media_Queries) per creare una [disposizione reattiva](https://developer.mozilla.org/docs/Web/Progressive_web_apps/Responsive/responsive_design_building_blocks) che funzioni bene sia su dispositivi desktop che mobili.
|
||||
|
||||
Ecco un esempio di una pagina cruscotto con applicato uno stile:
|
||||
|
||||

|
||||
|
||||
## Quiz Post-Lezione
|
||||
|
||||
[Quiz post-lezione](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/46?loc=it)
|
||||
|
||||
## Compito
|
||||
|
||||
[Refattorizzare e commentare il codice](assignment.it.md)
|
@@ -1,332 +0,0 @@
|
||||
# バンキングアプリを作ろう その 3: データの取得と利用方法
|
||||
|
||||
## レッスン前の小テスト
|
||||
|
||||
[レッスン前の小テスト](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/45?loc=ja)
|
||||
|
||||
### イントロダクション
|
||||
|
||||
すべての Web アプリケーションの中核には、*データ*があります。データには様々な形がありますが、その主な目的は常にユーザーに情報を表示することです。Web アプリケーションがますますインタラクティブで複雑になってきているため、ユーザーがどのように情報にアクセスして対話するかは、現在では Web 開発の重要な部分となっています。
|
||||
|
||||
このレッスンでは、サーバーから非同期にデータを取得し、このデータを使用して HTML をリロードせずに Web ページに情報を表示する方法を見ていきます。
|
||||
|
||||
### 前提条件
|
||||
|
||||
このレッスンでは、Web アプリの[ログインと登録フォーム](../../2-forms/translations/README.ja.md)の部分をビルドしておく必要があります。また、アカウントデータを取得するためには、ローカルに [Node.js](https://nodejs.org) と [サーバー API を実行する](../../api/translations/README.ja.md)をインストールする必要があります。
|
||||
|
||||
ターミナルで以下のコマンドを実行することで、サーバーが正常に動作していることを確認できます。
|
||||
|
||||
```sh
|
||||
curl http://localhost:5000/api
|
||||
# -> 結果として "Bank API v1.0.0" を返す必要があります。
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## AJAX とデータ取得
|
||||
|
||||
従来の Web サイトでは、ユーザーがリンクをクリックしたり、フォームを使用してデータを送信したりした際に、HTML ページ全体をリロードすることで表示されるコンテンツを更新しています。新しいデータの読み込みが必要になるたびに、Web サーバはブラウザで処理する必要のある新しい HTML ページを返し、現在のユーザのアクションを中断し、リロード中のインタラクションを制限します。このワークフローは、*マルチページアプリケーション* または *MPA* とも呼ばれます。
|
||||
|
||||

|
||||
|
||||
Web アプリケーションがより複雑でインタラクティブになり始めた頃、[AJAX (Asynchronous JavaScript and XML)](https://en.wikipedia.org/wiki/Ajax_(programming))と呼ばれる新しい技術が登場しました。この技術は、Web アプリケーションが HTML ページをリロードすることなく、JavaScript を使ってサーバーから非同期にデータを送受信することを可能にし、結果として、より高速な更新とよりスムーズなユーザーのインタラクションを実現します。サーバーから新しいデータを受信すると、[DOM](https://developer.mozilla.org/ja/docs/Web/API/Document_Object_Model) API を使用して現在の HTML ページを JavaScript で更新することもできます。時間の経過とともに、このアプローチは現在では [*シングルページアプリケーション* または *SPA*](https://en.wikipedia.org/wiki/Single-page_application) と呼ばれているものへと発展してきました。
|
||||
|
||||

|
||||
|
||||
AJAX が最初に導入されたとき、データを非同期で取得できる唯一の API は [`XMLHttpRequest`](https://developer.mozilla.org/ja/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest) でした。しかし、現在のブラウザはより便利で強力な [`Fetch` API](https://developer.mozilla.org/ja/docs/Web/API/Fetch_API) を実装しており、Promise を使用し、JSON データを操作するのに適しています。
|
||||
|
||||
> 最新のブラウザはすべて `Fetch API` をサポートしていますが、もしあなたの Web アプリケーションをレガシーブラウザや古いブラウザで動作させたいのであれば、最初に [caniuse.com の互換性テーブル](https://caniuse.com/fetch) をチェックするのが良いでしょう。
|
||||
|
||||
### タスク
|
||||
|
||||
[前回のレッスン](../../2-forms/translations/README.ja.md)では、アカウントを作成するための登録フォームを暗示しました。ここでは、既存のアカウントを使ってログインし、そのデータを取得するコードを追加します。`app.js` ファイルを開き、新しい `login` 関数を追加します。
|
||||
|
||||
```js
|
||||
async function login() {
|
||||
const loginForm = document.getElementById('loginForm')
|
||||
const user = loginForm.user.value;
|
||||
}
|
||||
```
|
||||
|
||||
ここではまずフォーム要素を `getElementById()` で取得し、入力からユーザ名を `loginForm.user.value` で取得します。すべてのフォームコントロールは、フォームのプロパティとしてその名前 (HTML では `name` 属性を使って設定されています) によってアクセスすることができます。
|
||||
|
||||
登録の際に行ったことと同様の方法で、フォームコントロールにアクセスすることができます。サーバーリクエストを実行するために別の関数を作成しますが、今回はアカウントデータを取得します。
|
||||
|
||||
```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' };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
ここでは `fetch` API を使ってサーバから非同期にデータを要求しますが、今回はデータを問い合わせるだけなので、呼び出す URL 以外の余分なパラメータは必要ありません。デフォルトでは、`fetch` は [`GET`](https://developer.mozilla.org/ja/docs/Web/HTTP/Methods/GET) HTTP リクエストを作成します。
|
||||
|
||||
✅ `encodeURIComponent()` は URL の特殊文字をエスケープする関数です。この関数を呼び出さずに URL の `user` の値を直接使うと、どのような問題が発生する可能性があるのでしょうか?
|
||||
|
||||
それでは、`login` 関数を `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');
|
||||
}
|
||||
```
|
||||
|
||||
まず、`getAccount` は非同期関数なので、これを `await` キーワードにマッチさせてサーバの結果を待つ必要があります。他のサーバからのリクエストと同様に、エラーが発生した場合にも対処しなければなりません。今のところ、エラーを表示するためのログメッセージを追加して、それをレイヤーに戻すだけです。
|
||||
|
||||
その後、データをどこかに保存しておく必要があるので、後でダッシュボードの情報を表示するためにデータを使用することができます。変数 `account` はまだ存在しないので、ファイルの先頭にグローバル変数を作成します。
|
||||
|
||||
```js
|
||||
let account = null;
|
||||
```
|
||||
|
||||
ユーザデータが変数に保存された後、既に持っている `navigate()` 関数を使って *ログイン* ページから *ダッシュボード* に移動することができます。
|
||||
|
||||
最後に、HTML を修正してログインフォームが送信されたときに `login` 関数を呼び出す必要があります。
|
||||
|
||||
```html
|
||||
<form id="loginForm" action="javascript:login()">
|
||||
```
|
||||
|
||||
すべてが正しく動作していることをテストします。新しいアカウントを登録して、同じアカウントを使ってログインしようとすることで、`register` 関数を完成させることができます。
|
||||
|
||||
次の部分に移る前に、関数の下部にこれを追加することで `register` 関数を完成させることもできます。
|
||||
|
||||
```js
|
||||
account = result;
|
||||
navigate('/dashboard');
|
||||
```
|
||||
|
||||
✅ デフォルトでは、閲覧している Web ページよりも*同じドメインとポート*からしかサーバー API を呼び出すことができないことをご存知でしょうか? これはブラウザによって強制されたセキュリティメカニズムです。しかし待ってください、私たちの Web アプリは `localhost:3000` で動作していますが、サーバー API は `localhost:5000` で動作しています。[Cross-Origin Resource Sharing (CORS)](https://developer.mozilla.org/ja/docs/Web/HTTP/CORS)と呼ばれる技術を使用することで、サーバーがレスポンスに特殊なヘッダを追加し、特定のドメインの例外を許可することで、クロスオリジンの HTTP リクエストを実行することが可能になります。
|
||||
|
||||
## データを表示するために HTML を更新する
|
||||
|
||||
ユーザーデータが手に入ったので、それを表示するために既存の HTML を更新しなければなりません。DOM から要素を取得する方法は既に知っていますが、例として `document.getElementById()` を使用します。ベースとなる要素ができたら、それを修正したり、子要素を追加したりするために使えるAPIをいくつか紹介します。
|
||||
|
||||
- 要素のテキストを変更するには、[`textContent`](https://developer.mozilla.org/ja/docs/Web/API/Node/textContent)プロパティを使用します。この値を変更すると、(あれば) すべての要素の子要素が削除され、提供されたテキストに置き換えられることに注意してください。このように、与えられた要素に空の文字列 `''` を代入することで、与えられた要素のすべての子を削除する効率的な方法でもあります。
|
||||
|
||||
- [`document.createElement()`](https://developer.mozilla.org/ja/docs/Web/API/Document/createElement) と [`append()`](https://developer.mozilla.org/ja/docs/Web/API/ParentNode/append) メソッドを使用すると、1つ以上の新しい子要素を作成して添付することができます
|
||||
|
||||
✅ 要素の [`innerHTML`](https://developer.mozilla.org/ja/docs/Web/API/Element/innerHTML) プロパティを使用して HTML の内容を変更することも可能ですが、これは [クロスサイトスクリプティング (XSS)](https://developer.mozilla.org/ja/docs/Glossary/Cross-site_scripting) 攻撃に対して脆弱なので避けるべきです。
|
||||
|
||||
### タスク
|
||||
|
||||
ダッシュボード画面に移る前に、*ログイン* ページでもう一つやるべきことがあります。現在、存在しないユーザ名でログインしようとすると、コンソールにメッセージが表示されますが、通常のユーザの場合は何も変わらず、何が起こっているのかわかりません。
|
||||
|
||||
必要に応じてエラーメッセージを表示できるように、ログインフォームにプレースホルダ要素を追加してみましょう。良い場所はログインの直前の `<button>` です。
|
||||
|
||||
```html
|
||||
...
|
||||
<div id="loginError"></div>
|
||||
<button>Login</button>
|
||||
...
|
||||
```
|
||||
|
||||
この `<div>` 要素は空で、コンテンツを追加するまで何も表示されません。また、JavaScript で簡単に取得できるように `id` を与えています。
|
||||
|
||||
ファイル `app.js` に戻り、新しいヘルパー関数 `updateElement` を作成します。
|
||||
|
||||
```js
|
||||
function updateElement(id, text) {
|
||||
const element = document.getElementById(id);
|
||||
element.textContent = text;
|
||||
}
|
||||
```
|
||||
|
||||
これは非常に簡単で、要素 *id* と *text* が与えられると、一致する `id` で DOM 要素のテキスト内容を更新します。このメソッドを `login` 関数の先ほどのエラーメッセージの代わりに使ってみましょう。
|
||||
|
||||
```js
|
||||
if (data.error) {
|
||||
return updateElement('loginError', data.error);
|
||||
}
|
||||
```
|
||||
|
||||
無効なアカウントでログインしようとすると、このように表示されるはずです。
|
||||
|
||||

|
||||
|
||||
これで、視覚的にはエラーテキストが表示されるようになりましたが、スクリーンリーダーで試してみると、何もアナウンスされないことに気づくでしょう。ページに動的に追加されたテキストをスクリーンリーダーでアナウンスするには、[ライブリージョン](https://developer.mozilla.org/ja/docs/Web/Accessibility/ARIA/ARIA_Live_Regions) と呼ばれるものを使用する必要があります。ここでは、アラートと呼ばれる特定のタイプのライブリージョンを使用するつもりです。
|
||||
|
||||
```html
|
||||
<div id="loginError" role="alert"></div>
|
||||
```
|
||||
|
||||
`register` 関数のエラーに対しても同様の動作を実装する (HTML の更新を忘れずに)。
|
||||
|
||||
## ダッシュボードに情報を表示する
|
||||
|
||||
先ほど見たのと同じテクニックを使って、ダッシュボード・ページにアカウント情報を表示してみましょう。
|
||||
|
||||
サーバから受け取ったアカウントオブジェクトはこんな感じです。
|
||||
|
||||
```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 }
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
> 注: 生活を楽にするためには、すでにデータが登録されている `test` アカウントを使用することができます。
|
||||
|
||||
### タスク
|
||||
|
||||
まずは HTML の「バランス」の部分を置き換えてプレースホルダ要素を追加してみましょう。
|
||||
|
||||
```html
|
||||
<section>
|
||||
Balance: <span id="balance"></span><span id="currency"></span>
|
||||
</section>
|
||||
```
|
||||
|
||||
また、アカウントの説明を表示するためのセクションをすぐ下に追加します。
|
||||
|
||||
```html
|
||||
<h2 id="description"></h2>
|
||||
```
|
||||
|
||||
✅ アカウントの説明は、その下にあるコンテンツのタイトルとして機能するため、意味的には見出しとしてマークアップされます。アクセシビリティにとって[見出し構造](https://www.nomensa.com/blog/2017/how-structure-headings-web-accessibility)がどのように重要であるかについて詳しく知り、他に何が見出しになり得るかを判断するためにページを批判的に見てみましょう。
|
||||
|
||||
次に、プレースホルダを埋めるための新しい関数を `app.js` に作成します。
|
||||
|
||||
```js
|
||||
function updateDashboard() {
|
||||
if (!account) {
|
||||
return navigate('/login');
|
||||
}
|
||||
|
||||
updateElement('description', account.description);
|
||||
updateElement('balance', account.balance.toFixed(2));
|
||||
updateElement('currency', account.currency);
|
||||
}
|
||||
```
|
||||
|
||||
まず、先に進む前に必要なアカウントデータがあることを確認します。次に、先ほど作成した `updateElement()` 関数を使って HTML を更新します。
|
||||
|
||||
> 残高表示をよりきれいにするために、メソッド [`toFixed(2)`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed) を使用して、小数点以下2桁の値を強制的に表示します。
|
||||
|
||||
これで、ダッシュボードがロードされるたびに `updateDashboard()` 関数を呼び出す必要があります。既に [レッスン1の課題](../../1-template-route/translations/README.ja.md) を終了している場合は簡単ですが、そうでない場合は以下の実装を使用することができます。
|
||||
|
||||
このコードを `updateRoute()` 関数の最後に追加します。
|
||||
|
||||
```js
|
||||
if (typeof route.init === 'function') {
|
||||
route.init();
|
||||
}
|
||||
```
|
||||
|
||||
ルート定義を更新します。
|
||||
|
||||
```js
|
||||
const routes = {
|
||||
'/login': { templateId: 'login' },
|
||||
'/dashboard': { templateId: 'dashboard', init: updateDashboard }
|
||||
};
|
||||
```
|
||||
|
||||
この変更により、ダッシュボードページが表示されるたびに関数 `updateDashboard()` が呼び出されるようになりました。ログインすると、アカウントの残高、通貨、説明が表示されるようになります。
|
||||
|
||||
## HTML テンプレートを使用してテーブルの行を動的に作成
|
||||
|
||||
[最初のレッスン](../../1-template-route/translations/README.ja.md)では、アプリのナビゲーションを実装するために HTML テンプレートを [`appendChild()`](https://developer.mozilla.org/ja/docs/Web/API/Node/appendChild) メソッドと一緒に使用しました。テンプレートは小さくして、ページの繰り返し部分を動的に埋め込むために使用することもできます。
|
||||
|
||||
同様のアプローチを使用して、HTML テーブルにトランザクションのリストを表示します。
|
||||
|
||||
### タスク
|
||||
|
||||
HTML `<body>` に新しいテンプレートを追加します。
|
||||
|
||||
```html
|
||||
<template id="transaction">
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</template>
|
||||
```
|
||||
|
||||
このテンプレートは1つのテーブル行を表し、3つのカラムを入力します。*日付*、*オブジェクト*、トランザクションの*金額*です。
|
||||
|
||||
次に、この `id` プロパティをダッシュボードテンプレート内のテーブルの `<tbody>` 要素に追加すると、JavaScript を使って見つけやすくなります。
|
||||
|
||||
```html
|
||||
<tbody id="transactions"></tbody>
|
||||
```
|
||||
|
||||
HTML の準備ができたので、JavaScript のコードに切り替えて新しい関数 `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;
|
||||
}
|
||||
```
|
||||
|
||||
この関数はその名前が示す通りのことを行います。先ほど作成したテンプレートを使って新しいテーブルの行を作成し、トランザクションデータを使ってその内容を埋めます。先ほど作成したテンプレートを使って新しいテーブル行を作成し、トランザクションデータを使って内容を埋めます。
|
||||
|
||||
```js
|
||||
const transactionsRows = document.createDocumentFragment();
|
||||
for (const transaction of account.transactions) {
|
||||
const transactionRow = createTransactionRow(transaction);
|
||||
transactionsRows.appendChild(transactionRow);
|
||||
}
|
||||
updateElement('transactions', transactionsRows);
|
||||
```
|
||||
|
||||
ここでは [`document.createDocumentFragment()`](https://developer.mozilla.org/ja/docs/Web/API/Document/createDocumentFragment) というメソッドを使用しています。これは新しい DOM フラグメントを作成し、その上で作業を行い、最終的にそれを HTML テーブルにアタッチする前のものです。
|
||||
|
||||
このコードが動作する前に、もう一つやらなければならないことがあります。というのは、現在のところ `updateElement()` 関数はテキストコンテンツのみをサポートしているからです。コードを少し変更してみましょう。
|
||||
|
||||
```js
|
||||
function updateElement(id, textOrNode) {
|
||||
const element = document.getElementById(id);
|
||||
element.textContent = ''; // Removes all children
|
||||
element.append(textOrNode);
|
||||
}
|
||||
```
|
||||
|
||||
私たちは [`append()`](https://developer.mozilla.org/docs/Web/API/ParentNode/append) メソッドを使用しています。これにより、テキストや [DOM Nodes](https://developer.mozilla.org/ja/docs/Web/API/Node) を親要素にアタッチすることができ、すべてのユースケースに最適です。
|
||||
|
||||
`test` アカウントを使ってログインしてみると、ダッシュボード上にトランザクションリストが表示されるはずです 🎉。
|
||||
|
||||
---
|
||||
|
||||
## 🚀 チャレンジ
|
||||
|
||||
ダッシュボードページを実際の銀行アプリのように見せるために一緒に作業しましょう。すでにアプリのスタイルを設定している場合は、[メディアクエリ](https://developer.mozilla.org/ja/docs/Web/CSS/Media_queries)を使用して、デスクトップとモバイルデバイスの両方でうまく機能する[レスポンシブデザイン](https://developer.mozilla.org/docs/Web/Progressive_web_apps/Responsive/responsive_design_building_blocks)を作成してみてください。
|
||||
|
||||
ダッシュボードページのスタイリング例です。
|
||||
|
||||

|
||||
|
||||
## レッスン後の小テスト
|
||||
|
||||
[レッスン後の小テスト](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/46?loc=ja)
|
||||
|
||||
## 課題
|
||||
|
||||
[コードのリファクタとコメント](assignment.ja.md)
|
@@ -1,326 +0,0 @@
|
||||
# 은행 앱 제작하기 파트 3: 데이터를 가져오고 사용하는 방식
|
||||
|
||||
## 강의 전 퀴즈
|
||||
|
||||
[Pre-lecture quiz](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/45?loc=ko)
|
||||
|
||||
### 소개
|
||||
|
||||
모든 웹 애플리케이션의 핵심에는 *data*가 있습니다. 데이터는 다양한 폼을 가질 수 있지만, 기본적인 목적은 항상 사용자에게 정보를 보여준다는 것입니다. 웹 앱이 점점 더 상호 작용하고 복잡해지면서, 사용자가 정보에 접근하고 상호 작용하는 방식은 이제 웹 개발에서 중요한 부분입니다.
|
||||
|
||||
이 강의에서는, 서버에서 비동기로 데이터를 가져오고, 이 데이터로 HTML을 다시 불러오지 않으면서 웹 페이지에 보여주는 방법으로 살펴봅니다.
|
||||
|
||||
### 준비물
|
||||
|
||||
이 강의에서 웹 앱의 [Login and Registration Form](../../2-forms/translations/README.ko.md) 부분을 작성하는 것이 필요합니다. 또한 계정 데이터를 가져오려면 [Node.js](https://nodejs.org)와 [run the server API](../../api/README.md)를 로컬에 설치해야 합니다.
|
||||
|
||||
터미널에서 이 명령을 실행하여 서버가 실행되고 있는지 테스트할 수 있습니다:
|
||||
|
||||
```sh
|
||||
curl http://localhost:5000/api
|
||||
# -> should return "Bank API v1.0.0" as a result
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## AJAX와 데이터 가져오기
|
||||
|
||||
기존 웹 사이트는 모든 HTML 페이지를 다시 불러오기 위해서 사용자가 링크를 클릭하거나 폼으로 데이터를 제출할 때 표시되는 콘텐츠를 갱신합니다. 새로운 데이터를 불러와야 할 때마다, 웹 서버는 브라우저에서 처리하는 새 HTML 페이지를 반환하여, 현재 사용자의 액션을 중단하고 다시 불러오는 동안 상호 작용을 제한합니다. 이 과정을 *Multi-Page Application* 혹은 *MPA*라고 합니다.
|
||||
|
||||

|
||||
|
||||
웹 애플리케이션이 더 복잡해지고 상호 작용하기 시작하면서, [AJAX (Asynchronous JavaScript and XML)](https://en.wikipedia.org/wiki/Ajax_(programming))이라는 새로운 기술이 나타났습니다. 이 기술을 쓰면 웹 앱은 HTML 페이지를 다시 불러오지 않고, JavaScript를 사용하여 비동기로 서버에서 데이터를 보내고 찾을 수 있으므로, 갱신 속도가 빨라지고 사용자 상호 작용이 부드러워집니다. 서버에서 새로운 데이터를 받으면, [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model) API로 현재 HTML 페이지를 JavaScript로 갱신할 수도 있습니다. 시간이 지나면서, 이 방식은 이제 [*Single-Page Application* or *SPA*](https://en.wikipedia.org/wiki/Single-page_application)라는 것으로 발전했습니다.
|
||||
|
||||

|
||||
|
||||
AJAX가 처음 소개되었을 때, 데이터를 비동기로 가져올 유일한 API는 [`XMLHttpRequest`](https://developer.mozilla.org/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest)였습니다. 그러나 모던 브라우저는 이제 promises를 사용하고 JSON 데이터를 조작할 때 적당하며, 더 편리하고 강력한 [`Fetch` API](https://developer.mozilla.org/docs/Web/API/Fetch_API)도 구현합니다.
|
||||
|
||||
> 모든 모던 브라우저는 `Fetch API`를 지원하지만, 웹 애플리케이션이 레거시 혹은 옛날 브라우저에서 작동하도록 하려면 항상 [compatibility table on caniuse.com](https://caniuse.com/fetch)를 먼저 보는 것이 좋습니다.
|
||||
|
||||
### 작업
|
||||
|
||||
[이전 강의](../../2-forms/translations/README.ko.md)에서는 계정을 만들기 위해 가입 폼을 구현했습니다. 이제 이미 있는 계정으로 로그인하는 코드를 추가하고, 데이터를 가져올 것 입니다. `app.js` 파일을 열고 새로운 `login` 함수를 추가합니다:
|
||||
|
||||
```js
|
||||
async function login() {
|
||||
const loginForm = document.getElementById('loginForm')
|
||||
const user = loginForm.user.value;
|
||||
}
|
||||
```
|
||||
|
||||
여기서 `getElementById()`로 폼 요소를 찾는 것으로 시작한 다음, `loginForm.user.value`로 입력에서 username을 가져옵니다. 모든 폼 컨트롤은 폼의 속성에 있는 이름(HTML에서 `name`속성으로 설정)으로 제어할 수 있습니다.
|
||||
|
||||
가입을 위해서 작업했던 것과 비슷한 방식으로, 서버 요청하는 또 다른 함수를 만들지만, 이번에는 계정 데이터를 찾기 위한 것입니다:
|
||||
|
||||
```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' };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
비동기로 서버에 데이터를 요청하기 위해서 `fetch` API를 사용하지만, 이번에는 데이터만 쿼리하므로, 호출할 URL 이외 추가 파라미터는 필요하지 않습니다. 기본적으로, `fetch`는 여기에서 찾는 것처럼 [`GET`](https://developer.mozilla.org/docs/Web/HTTP/Methods/GET) 요청을 생성합니다.
|
||||
|
||||
✅ `encodeURIComponent()`는 URL에 대한 특수 문자를 이스케이프하는 함수입니다. 이 함수를 호출하지 않고 URL에서 `user` 값을 직접 사용하면 어떤 이슈가 발생할 수 있나요?
|
||||
|
||||
이제 `getAccount`를 사용하여 `login` 함수를 갱신합니다:
|
||||
|
||||
```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');
|
||||
}
|
||||
```
|
||||
|
||||
먼저, `getAccount`는 비동기 함수이므로 서버 결과를 기다리려면 `await` 키워드로 맞춰야 합니다. 모든 서버 요청처럼, 오류 케이스도 처리해야 합니다. 지금은 오류를 보여주는 로그 메시지만 추가하고, 이 레이어로 돌아옵니다.
|
||||
|
||||
그러고 나중에 대시보드 정보를 보여줄 수 있도록 데이터를 어딘가 저장해야 합니다. `account` 변수가 아직 없으므로, 파일 상단에 전역 변수를 생성합니다:
|
||||
|
||||
```js
|
||||
let account = null;
|
||||
```
|
||||
|
||||
사용자 데이터가 변수에 저장되면 이미 있는 `navigate()` 함수를 사용하여 *login* 페이지에서 *dashboard*로 이동할 수 있습니다.
|
||||
|
||||
마지막으로, HTML을 수정하여 로그인 폼을 제출할 때마다 `login` 함수를 호출해야 합니다:
|
||||
|
||||
```html
|
||||
<form id="loginForm" action="javascript:login()">
|
||||
```
|
||||
|
||||
새로운 계정을 가입하고 같은 계정으로 로그인을 시도하여 모두 잘 작동하는지 테스트합니다.
|
||||
|
||||
다음 부분으로 가기 전에, 함수 하단에 다음을 추가하여 `register` 함수를 완성할 수도 있습니다:
|
||||
|
||||
```js
|
||||
account = result;
|
||||
navigate('/dashboard');
|
||||
```
|
||||
|
||||
✅ 기본적으로, 보고있는 웹 페이지에 *동일한 도메인와 포트*에서만 서버 API를 호출할 수 있다는 사실을 알고 있었나요? 이것은 브라우저에 의해 시행되는 보안 메커니즘입니다. 하지만, 웹 앱은 `localhost:3000`에서 실행되고 서버 API가 `localhost:5000`에서 실행됩니다. 왜 작동할까요? [Cross-Origin Resource Sharing (CORS)](https://developer.mozilla.org/docs/Web/HTTP/CORS)라는 기술을 사용하면 서버가 응답에 특별한 헤더를 추가하여 특정 도메인에 대한 예외를 허용하므로, cross-origin HTTP 요청을 수행 할 수 있습니다.
|
||||
|
||||
## 데이터를 보여주기 위해 HTML 갱신하기
|
||||
|
||||
이제 사용자 데이터가 있으므로, 기존 HTML을 갱신해서 보여줘야 합니다. 예시로 `document.getElementById()`를 사용하여 DOM에서 요소를 검색하는 방법은 이미 있습니다. 바탕 요소가 있으면, 수정하거나 하위 요소를 추가하는 방식으로 사용할 수 있는 몇 가지 API가 있습니다:
|
||||
|
||||
- [`textContent`](https://developer.mozilla.org/docs/Web/API/Node/textContent) 속성을 사용하여 요소의 텍스트를 바꿀 수 있습니다. 이 값을 변경하면 모든 요소의 하위가(있는 경우) 제거되고 주어진 텍스트로 대체됩니다. 따라서, 빈 문자열 `''`을 할당하여 주어진 요소의 모든 하위를 제거하는 효율적인 방법일 수도 있습니다.
|
||||
|
||||
- [`document.createElement()`](https://developer.mozilla.org/docs/Web/API/Document/createElement)를 [`append()`](https://developer.mozilla.org/docs/Web/API/ParentNode/append)메소드와 함께 사용하면 하나 이상의 새로운 하위 요소를 만들고 붙일 수 있습니다.
|
||||
|
||||
✅ 요소의 [`innerHTML`](https://developer.mozilla.org/docs/Web/API/Element/innerHTML) 속성을 사용하여 HTML 내용을 바꿀 수 있지만, [cross-site scripting (XSS)](https://developer.mozilla.org/docs/Glossary/Cross-site_scripting) 공격에 취약하므로 피해야 합니다.
|
||||
|
||||
### 작업
|
||||
|
||||
대시보드 화면으로 이동하기 전에, *login* 페이지에서 할 일이 더 있습니다. 현재, 없는 사용자 이름으로 로그인하면, 콘솔에는 메시지가 보이지만 일반적인 사용자의 경우에는 하나도 안 바뀌므로 어떤 일이 나는지 알 수 없습니다.
|
||||
|
||||
필요한 경우에 오류 메시지를 볼 수 있는 로그인 폼에 placeholder 요소를 추가하겠습니다. 로그인 `<button>` 바로 전에 두는 것이 좋습니다:
|
||||
|
||||
```html
|
||||
...
|
||||
<div id="loginError"></div>
|
||||
<button>Login</button>
|
||||
...
|
||||
```
|
||||
|
||||
이 `<div>` 요소는 비어 있으므로, 내용를 더 할 때까지 화면에 아무것도 나오지 않습니다. 또한 JavaScript로 쉽게 찾을 수 있도록 `id`를 제공합니다.
|
||||
|
||||
`app.js` 파일로 돌아오고 새로운 헬퍼 함수인 `updateElement`를 만듭니다:
|
||||
|
||||
```js
|
||||
function updateElement(id, text) {
|
||||
const element = document.getElementById(id);
|
||||
element.textContent = text;
|
||||
}
|
||||
```
|
||||
|
||||
이는 매우 간편합니다, *id*와 *text* 요소가 주어지는 순간에 일치하는 `id`로 DOM 요소의 텍스트 내용이 갱신됩니다. `login` 함수의 이전 오류 메시지 대신에 이 방법을 사용하겠습니다:
|
||||
|
||||
```js
|
||||
if (data.error) {
|
||||
return updateElement('loginError', data.error);
|
||||
}
|
||||
```
|
||||
|
||||
이제 유효하지 않은 계정으로 로그인 시도하면, 다음과 같이 보입니다:
|
||||
|
||||

|
||||
|
||||
`register` 함수 오류와 동일한 동작을 하도록 구현합니다 (HTML을 갱신하는 것을 잊지 마세요).
|
||||
|
||||
## 대시보드로 정보 출력하기
|
||||
|
||||
방금 전 같은 기술을 사용하여 대시보드 페이지에서 계정 정보를 보여주는 작업도 합니다.
|
||||
|
||||
서버에서 받은 계정 객체는 다음과 같습니다:
|
||||
|
||||
```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: 더 쉽게 진행하기 위해서, 이미 데이터로 채워진 기존 `test` 계정을 사용할 수 있습니다.
|
||||
|
||||
### 작업
|
||||
|
||||
HTML의 "Balance" 섹션을 교체하고 placeholder 요소를 추가하는 것으로 시작합니다:
|
||||
|
||||
```html
|
||||
<section>
|
||||
Balance: <span id="balance"></span><span id="currency"></span>
|
||||
</section>
|
||||
```
|
||||
|
||||
또한 바로 밑에 새로운 섹션을 추가하여 계정 설명을 출력합니다:
|
||||
|
||||
```html
|
||||
<section id="description" aria-label="Account description"></section>
|
||||
```
|
||||
|
||||
✅ 이 섹션의 내용을 설명하는 텍스트 라벨이 없기 때문에, `aria-label` 속성을 사용하여 접근성 힌트를 줍니다. 모두 웹 앱에 접근할 수 있도록 [ARIA attributes](https://developer.mozilla.org/docs/Web/Accessibility/ARIA)에 대해 더 알아보세요.
|
||||
|
||||
다음으로, `app.js`에 placeholder를 채우기 위해서 새로운 함수를 만듭니다:
|
||||
|
||||
```js
|
||||
function updateDashboard() {
|
||||
if (!account) {
|
||||
return navigate('/login');
|
||||
}
|
||||
|
||||
updateElement('description', account.description);
|
||||
updateElement('balance', account.balance.toFixed(2));
|
||||
updateElement('currency', account.currency);
|
||||
}
|
||||
```
|
||||
|
||||
먼저, 나아가기 전 필요한 계정 데이터가 있는지 확인합니다. 그러고 일찍 만들어 둔 `updateElement()` 함수로 HTML을 업데이트합니다.
|
||||
|
||||
> 잔액을 더 예쁘게 보이게 만드려면, [`toFixed(2)`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed) 방법으로 소수점 이하 2자리 값을 강제로 출력합니다.
|
||||
|
||||
이제 대시보드를 불러올 때마다 `updateDashboard()` 함수를 호출해야 합니다. 이미 [lesson 1 assignment](../../1-template-route/assignment.md)를 완료했다면 간단해야 합니다. 그렇지 않다면 이미 구현된 내용으로 쓸 수 있습니다.
|
||||
|
||||
`updateRoute()` 함수 끝에 이 코드를 추가합니다:
|
||||
|
||||
```js
|
||||
if (typeof route.init === 'function') {
|
||||
route.init();
|
||||
}
|
||||
```
|
||||
|
||||
그리고 라우터 정의를 업데이트합니다:
|
||||
|
||||
```js
|
||||
const routes = {
|
||||
'/login': { templateId: 'login' },
|
||||
'/dashboard': { templateId: 'dashboard', init: updateDashboard }
|
||||
};
|
||||
```
|
||||
|
||||
이 변경점으로, 대시보드 페이지가 나올 때마다 `updateDashboard()` 함수가 호출됩니다. 로그인하고나서, 계정 잔액, 통화와 설명을 볼 수 있습니다.
|
||||
|
||||
## HTML 템플릿으로 동적 테이블 row 만들기
|
||||
|
||||
[first lesson](../../1-template-route/translations/README.ko.md)에서는 HTML 템플릿과 [`appendChild()`](https://developer.mozilla.org/docs/Web/API/Node/appendChild) 메소드로 앱의 탐색을 구현했습니다. 템플릿은 더 작아 질 수 있고 페이지의 반복적인 부분을 동적으로 채우는 데 쓸 수 있습니다.
|
||||
|
||||
유사한 접근 방식을 사용하여 HTML 테이블에 트랜잭션 목록을 출력합니다.
|
||||
|
||||
### 작업
|
||||
|
||||
HTML `<body>`에서 새로운 템플릿을 추가합니다:
|
||||
|
||||
```html
|
||||
<template id="transaction">
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</template>
|
||||
```
|
||||
|
||||
이 템플릿은 하나의 테이블 행을 나타내고 있으며, 앞으로 채워나갈 3 개의 열이 있습니다: *date*, *object* 그리고 트랜젝션의 *amount*.
|
||||
|
||||
그러고, 이 `id` 속성을 대시보드 템플릿 내 테이블의 `<tbody>` 요소에 추가하여 JavaScript로 쉽게 찾을 수 있게 작성합니다:
|
||||
|
||||
```html
|
||||
<tbody id="transactions"></tbody>
|
||||
```
|
||||
|
||||
HTML은 준비되었습니다, JavaScript 코드로 전환하고 새로운 함수 `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;
|
||||
}
|
||||
```
|
||||
|
||||
이 함수는 이름이 의미한대로 정확히 수행합니다: 이전에 만든 템플릿을 사용하면서, 새 테이블 행을 만들고 트랜잭션 데이터로 내용을 채웁니다. `updateDashboard()` 함수에서 이것으로 테이블을 채울 것입니다:
|
||||
|
||||
```js
|
||||
const transactionsRows = document.createDocumentFragment();
|
||||
for (const transaction of account.transactions) {
|
||||
const transactionRow = createTransactionRow(transaction);
|
||||
transactionsRows.appendChild(transactionRow);
|
||||
}
|
||||
updateElement('transactions', transactionsRows);
|
||||
```
|
||||
|
||||
여기서는 새로운 DOM 프래그먼트를 만들 [`document.createDocumentFragment()`](https://developer.mozilla.org/docs/Web/API/Document/createDocumentFragment) 메소드로 최종적인 HTML 테이블에 붙입니다.
|
||||
|
||||
현재 `updateElement()` 함수가 텍스트 내용만 지원하므로 이 코드가 실행되기 전에 할 일이 하나 더 있습니다. 코드를 약간 바꿔 보겠습니다:
|
||||
|
||||
```js
|
||||
function updateElement(id, textOrNode) {
|
||||
const element = document.getElementById(id);
|
||||
element.textContent = ''; // Removes all children
|
||||
element.append(textOrNode);
|
||||
}
|
||||
```
|
||||
|
||||
[`append()`](https://developer.mozilla.org/docs/Web/API/ParentNode/append)] 메소드를 사용하면 상위 요소에 텍스트 혹은 [DOM Nodes](https://developer.mozilla.org/docs/Web/API/Node)를 붙일 수 있으므로, 모든 사용 케이스에 적당합니다.
|
||||
|
||||
`test` 계정을 사용하여 로그인을 해보면, 지금 대시보드에 거래 목록이 보입니다 🎉.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 도전
|
||||
|
||||
대시보드 페이지를 실제 은행 앱처럼 보이도록 함께 작업해보세요. 이미 앱 스타일한 경우, [media queries](https://developer.mozilla.org/docs/Web/CSS/Media_Queries)를 사용하여 데스크톱과 모바일 장치 다 잘 작동하는 [responsive design](https://developer.mozilla.org/docs/Web/Progressive_web_apps/Responsive/responsive_design_building_blocks)으로 만들어보세요.
|
||||
|
||||
여기는 꾸며진 대시보드 페이지의 예시입니다:
|
||||
|
||||

|
||||
|
||||
## 강의 후 퀴즈
|
||||
|
||||
[Post-lecture quiz](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/46?loc=ko)
|
||||
|
||||
## 과제
|
||||
|
||||
[Refactor and comment your code](../assignment.md)
|
@@ -1,334 +0,0 @@
|
||||
# Bina Aplikasi Perbankan Bahagian 3: Kaedah Mengambil dan Menggunakan Data
|
||||
|
||||
## Kuiz Pra Kuliah
|
||||
|
||||
[Kuiz Pra Kuliah](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/45)
|
||||
|
||||
### Pengenalan
|
||||
|
||||
Inti setiap aplikasi web ada *data*. Data boleh dilakukan dalam pelbagai bentuk, tetapi tujuan utamanya adalah untuk menunjukkan maklumat kepada pengguna. Dengan aplikasi web menjadi semakin interaktif dan kompleks, bagaimana pengguna mengakses dan berinteraksi dengan maklumat kini menjadi bahagian penting dalam pembangunan web.
|
||||
|
||||
Dalam pelajaran ini, kita akan melihat cara mengambil data dari pelayan secara asinkron, dan menggunakan data ini untuk memaparkan maklumat di laman web tanpa memuatkan semula HTML.
|
||||
|
||||
### Prasyarat
|
||||
|
||||
Anda perlu membina bahagian [Borang Log Masuk dan Pendaftaran](../2-forms/README.md) dari aplikasi web untuk pelajaran ini. Anda juga perlu memasang [Node.js](https://nodejs.org) dan [jalankan API pelayan](../api/README.md) secara tempatan supaya anda mendapat data akaun.
|
||||
|
||||
Anda boleh menguji bahawa pelayan berjalan dengan betul dengan menjalankan perintah ini di terminal:
|
||||
|
||||
```sh
|
||||
curl http://localhost:5000/api
|
||||
# -> harus mengembalikan "Bank API v1.0.0" sebagai hasilnya
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## AJAX dan pengambilan data
|
||||
|
||||
Laman web tradisional mengemas kini kandungan yang dipaparkan ketika pengguna memilih pautan atau mengirimkan data menggunakan borang, dengan memuatkan semula halaman HTML penuh. Setiap kali data baru dimuat, pelayan web mengembalikan halaman HTML baru yang perlu diproses oleh penyemak imbas, mengganggu tindakan pengguna semasa dan membatasi interaksi semasa muat semula. Alur kerja ini juga disebut *Aplikasi Multi-Halaman* atau *MPA*.
|
||||
|
||||

|
||||
|
||||
Ketika aplikasi web mula menjadi lebih kompleks dan interaktif, teknik baru yang disebut [AJAX (Asynchronous JavaScript and XML)](https://en.wikipedia.org/wiki/Ajax_(programming)) muncul. Teknik ini membolehkan aplikasi web mengirim dan mengambil data dari pelayan secara asinkron menggunakan JavaScript, tanpa harus memuat semula halaman HTML, menghasilkan kemas kini yang lebih cepat dan interaksi pengguna yang lebih lancar. Apabila data baru diterima dari pelayan, halaman HTML semasa juga dapat diperbarui dengan JavaScript menggunakan API [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model). Dari masa ke masa, pendekatan ini telah berkembang menjadi apa yang sekarang disebut [*Aplikasi Halaman Tunggal* atau *SPA*](https://en.wikipedia.org/wiki/Single-page_application).
|
||||
|
||||

|
||||
|
||||
Semasa AJAX pertama kali diperkenalkan, satu-satunya API yang tersedia untuk mengambil data secara asinkron adalah [`XMLHttpRequest`](https://developer.mozilla.org/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest). Tetapi penyemak imbas moden kini juga melaksanakan [`Fetch` API](https://developer.mozilla.org/docs/Web/API/Fetch_API), yang menggunakan janji dan lebih sesuai untuk dimanipulasi Data JSON.
|
||||
|
||||
> Walaupun semua penyemak imbas moden menyokong `Fetch API`, jika anda mahu aplikasi web anda berfungsi pada penyemak imbas lama atau penyemak imbas lama, sebaiknya periksa [jadual keserasian di caniuse.com](https://caniuse.com/fetch) pertama.
|
||||
|
||||
### Tugas
|
||||
|
||||
Dalam [pelajaran sebelumnya](../2-form/README.md) kami melaksanakan borang pendaftaran untuk membuat akaun. Kami sekarang akan menambahkan kod untuk log masuk menggunakan akaun yang ada, dan mengambil datanya. Buka fail `app.js` dan tambahkan fungsi `login` baru:
|
||||
|
||||
```js
|
||||
async function login() {
|
||||
const loginForm = document.getElementById('loginForm')
|
||||
const user = loginForm.user.value;
|
||||
}
|
||||
```
|
||||
|
||||
Di sini kita mulakan dengan mengambil elemen borang dengan `getElementById()`, dan kemudian kita mendapat nama pengguna dari input dengan `loginForm.user.value`. Setiap kawalan borang dapat diakses dengan namanya (diatur dalam HTML menggunakan atribut `name`) sebagai harta bentuk.
|
||||
|
||||
Dengan cara yang serupa dengan apa yang kami lakukan untuk pendaftaran, kami akan membuat fungsi lain untuk melakukan permintaan pelayan, tetapi kali ini untuk mengambil data akaun:
|
||||
|
||||
```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' };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Kami menggunakan API `fetch` untuk meminta data secara tidak serentak dari pelayan, tetapi kali ini kami tidak memerlukan parameter tambahan selain URL untuk dipanggil, kerana kami hanya meminta data. Secara lalai, `fetch` membuat permintaan HTTP [`GET`](https://developer.mozilla.org/docs/Web/HTTP/Methods/GET), itulah yang kami cari di sini.
|
||||
|
||||
✅ `encodeURIComponent()` adalah fungsi yang melarikan diri daripada watak khas untuk URL. Masalah apa yang mungkin kita hadapi jika kita tidak memanggil fungsi ini dan menggunakan nilai `pengguna` secara langsung di URL?
|
||||
|
||||
Sekarang mari kita kemas kini fungsi `login` kami untuk menggunakan `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');
|
||||
}
|
||||
```
|
||||
|
||||
Pertama, kerana `getAccount` adalah fungsi tak segerak, kita perlu memadankannya dengan kata kunci `await` untuk menunggu hasil pelayan. Seperti permintaan pelayan, kami juga harus menangani kes ralat. Buat masa ini kami hanya akan menambahkan mesej log untuk memaparkan ralat, dan kembali kepadanya kemudian.
|
||||
|
||||
Kemudian kita harus menyimpan data di suatu tempat sehingga nanti kita dapat menggunakannya untuk memaparkan maklumat papan pemuka. Oleh kerana pemboleh ubah `account` belum ada, kami akan membuat pemboleh ubah global untuknya di bahagian atas fail kami:
|
||||
|
||||
```js
|
||||
let account = null;
|
||||
```
|
||||
|
||||
Setelah data pengguna disimpan ke dalam variabel kita dapat menavigasi dari halaman *login* ke papan pemuka * menggunakan fungsi `navigate()` yang sudah kita miliki.
|
||||
|
||||
Akhirnya, kita perlu memanggil fungsi `login` kita semasa borang log masuk dihantar, dengan mengubah HTML:
|
||||
|
||||
```html
|
||||
<form id="loginForm" action="javascript:login()">
|
||||
```
|
||||
|
||||
Uji bahawa semuanya berfungsi dengan betul dengan mendaftarkan akaun baru dan cuba log masuk menggunakan akaun yang sama.
|
||||
|
||||
Sebelum beralih ke bahagian seterusnya, kita juga dapat menyelesaikan fungsi `register` dengan menambahkan ini di bahagian bawah fungsi:
|
||||
|
||||
```js
|
||||
account = result;
|
||||
navigate('/dashboard');
|
||||
```
|
||||
|
||||
✅ Tahukah anda bahawa secara lalai, anda hanya dapat memanggil API pelayan dari *domain dan port yang sama* daripada halaman web yang anda lihat? Ini adalah mekanisme keselamatan yang dikuatkuasakan oleh penyemak imbas. Tetapi tunggu, aplikasi web kami berjalan di `localhost: 3000` sedangkan API pelayan berjalan di `localhost: 5000`, mengapa ia berfungsi? Dengan menggunakan teknik yang disebut [Cross-Origin Resource Sharing (CORS)](https://developer.mozilla.org/docs/Web/HTTP/CORS), adalah mungkin untuk melakukan permintaan HTTP bersilang asal jika pelayan menambah tajuk khas untuk respons, yang memungkinkan pengecualian untuk domain tertentu.
|
||||
|
||||
> Ketahui lebih lanjut mengenai API dengan mengambil [pelajaran](https://docs.microsoft.com/learn/modules/use-apis-discover-museum-art/?WT.mc_id=academic-77807-sagibbon)
|
||||
|
||||
## Kemas kini HTML untuk memaparkan data
|
||||
|
||||
Sekarang kita mempunyai data pengguna, kita harus mengemas kini HTML yang ada untuk memaparkannya. Kami sudah tahu cara mendapatkan elemen dari DOM menggunakan contohnya `document.getElementById()`. Setelah anda mempunyai elemen asas, berikut adalah beberapa API yang boleh anda gunakan untuk mengubahnya atau menambahkan elemen anak padanya:
|
||||
|
||||
- Dengan menggunakan sifat [`textContent`](https://developer.mozilla.org/docs/Web/API/Node/textContent), anda boleh mengubah teks elemen. Perhatikan bahawa mengubah nilai ini akan membuang semua anak elemen (jika ada) dan menggantinya dengan teks yang disediakan. Oleh itu, ini juga kaedah yang berkesan untuk membuang semua anak dari elemen tertentu dengan memberikan string kosong "" kepadanya.
|
||||
|
||||
- Menggunakan [`document.createElement()`](https://developer.mozilla.org/docs/Web/API/DocumentcreateElement) bersama dengan [`append()`](https://developer.mozilla.org/docs/Web/API/ParentNode/append) kaedah anda boleh membuat dan melampirkan satu atau lebih elemen anak baru.
|
||||
|
||||
✅ Menggunakan sifat [`innerHTML`](https://developer.mozilla.org/docs/Web/API/Element/innerHTML) elemen juga mungkin untuk mengubah kandungan HTMLnya, tetapi yang ini harus dielakkan kerana terdedah kepada serangan [cross-site scripting (XSS)](https://developer.mozilla.org/docs/Glossary/Cross-site_scripting) serangan.
|
||||
|
||||
### Tugas
|
||||
|
||||
Sebelum beralih ke skrin papan pemuka, ada satu perkara lagi yang harus kita lakukan di halaman *log masuk*. Pada masa ini, jika anda cuba log masuk dengan nama pengguna yang tidak ada, mesej ditunjukkan di konsol tetapi untuk pengguna biasa tidak ada yang berubah dan anda tidak tahu apa yang berlaku.
|
||||
|
||||
Mari tambahkan elemen placeholder dalam borang log masuk di mana kita dapat memaparkan mesej ralat jika diperlukan. Tempat yang bagus adalah tepat sebelum log masuk `<button>`:
|
||||
|
||||
```html
|
||||
...
|
||||
<div id="loginError"></div>
|
||||
<button>Login</button>
|
||||
...
|
||||
```
|
||||
|
||||
Elemen `<div>` ini kosong, yang bermaksud bahawa tidak ada yang akan dipaparkan di layar sehingga kami menambahkan beberapa kandungan ke dalamnya. Kami juga memberikannya `id` sehingga kami dapat mengambilnya dengan mudah dengan JavaScript.
|
||||
|
||||
Kembali ke fail `app.js` dan buat fungsi pembantu baru `updateElement`:
|
||||
|
||||
```js
|
||||
function updateElement(id, text) {
|
||||
const element = document.getElementById(id);
|
||||
element.textContent = text;
|
||||
}
|
||||
```
|
||||
|
||||
Yang ini cukup mudah: diberi elemen *id* dan *teks*, ia akan mengemas kini kandungan teks elemen DOM dengan `id` yang sepadan. Mari gunakan kaedah ini sebagai ganti mesej ralat sebelumnya dalam fungsi `login`:
|
||||
|
||||
```js
|
||||
if (data.error) {
|
||||
return updateElement('loginError', data.error);
|
||||
}
|
||||
```
|
||||
|
||||
Sekiranya anda cuba log masuk dengan akaun yang tidak sah, anda akan melihat seperti ini:
|
||||
|
||||

|
||||
|
||||
Sekarang kita mempunyai teks ralat yang muncul secara visual, tetapi jika anda mencubanya dengan pembaca skrin, anda akan melihat bahawa tidak ada yang diumumkan. Agar teks yang ditambahkan secara dinamis ke halaman diumumkan oleh pembaca skrin, ia perlu menggunakan sesuatu yang disebut [Live Region](https://developer.mozilla.org/docs/Web/Accessibility/ARIA/ARIA_Live_Regions). Di sini kita akan menggunakan jenis kawasan langsung yang disebut amaran:
|
||||
|
||||
```html
|
||||
<div id="loginError" role="alert"></div>
|
||||
```
|
||||
|
||||
Terapkan tingkah laku yang sama untuk kesalahan fungsi `register` (jangan lupa untuk mengemas kini HTML).
|
||||
|
||||
## Paparkan maklumat di papan pemuka
|
||||
|
||||
Dengan menggunakan teknik yang sama seperti yang baru kita lihat, kita juga akan menjaga memaparkan maklumat akaun di halaman papan pemuka.
|
||||
|
||||
Seperti inilah objek akaun yang diterima dari pelayan:
|
||||
|
||||
```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 }
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
> Catatan: untuk menjadikan hidup anda lebih mudah, anda boleh menggunakan akaun `test` yang sudah ada yang sudah diisi dengan data.
|
||||
|
||||
### Tugas
|
||||
|
||||
Mari mulakan dengan mengganti bahagian "Baki" dalam HTML untuk menambahkan elemen ruang letak:
|
||||
|
||||
```html
|
||||
<section>
|
||||
Balance: <span id="balance"></span><span id="currency"></span>
|
||||
</section>
|
||||
```
|
||||
|
||||
Kami juga akan menambah bahagian baru di bawah untuk memaparkan keterangan akaun:
|
||||
|
||||
```html
|
||||
<h2 id="description"></h2>
|
||||
```
|
||||
|
||||
✅ Oleh kerana perihalan akaun berfungsi sebagai tajuk untuk kandungan di bawahnya, ia ditandakan secara semantik sebagai tajuk. Ketahui lebih lanjut mengenai bagaimana [struktur tajuk](https://www.nomensa.com/blog/2017/how-structure-headings-web-accessibility) penting untuk dilayari, dan lihat halaman secara kritis untuk menentukan apa lagi boleh menjadi tajuk.
|
||||
|
||||
Seterusnya, kami akan membuat fungsi baru di `app.js` untuk mengisi ruang letak:
|
||||
|
||||
```js
|
||||
function updateDashboard() {
|
||||
if (!account) {
|
||||
return navigate('/login');
|
||||
}
|
||||
|
||||
updateElement('description', account.description);
|
||||
updateElement('balance', account.balance.toFixed(2));
|
||||
updateElement('currency', account.currency);
|
||||
}
|
||||
```
|
||||
|
||||
Pertama, kami memeriksa bahawa kami mempunyai data akaun yang kami perlukan sebelum melangkah lebih jauh. Kemudian kami menggunakan fungsi `updateElement()` yang kami buat sebelumnya untuk mengemas kini HTML.
|
||||
|
||||
> Untuk menjadikan paparan keseimbangan lebih cantik, kami menggunakan kaedah [`toFixed(2)`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed) untuk daya memaparkan nilai dengan 2 digit selepas titik perpuluhan.
|
||||
|
||||
Sekarang kita perlu memanggil fungsi `updateDashboard()` setiap kali papan pemuka dimuat. Sekiranya anda sudah menyelesaikan [tugasan 1](../1-template-route/assignment.md) ini harus dilakukan dengan mudah, jika tidak, anda boleh menggunakan pelaksanaan berikut.
|
||||
|
||||
Tambahkan kod ini ke akhir fungsi `updateRoute()`:
|
||||
|
||||
```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 }
|
||||
};
|
||||
```
|
||||
|
||||
Dengan perubahan ini, setiap kali halaman dashboard ditampilkan, fungsi `updateDashboard()` dipanggil. Selepas log masuk, anda seharusnya dapat melihat baki akaun, mata wang dan keterangan.
|
||||
|
||||
## Buat baris jadual secara dinamik dengan templat HTML
|
||||
|
||||
Dalam [first lesson](../1-template-route/README.md) kami menggunakan templat HTML bersama dengan [`appendChild()`](https://developer.mozilla.org/docs/Web/API/Node/appendChild) kaedah untuk melaksanakan navigasi di aplikasi kami. Templat juga dapat lebih kecil dan digunakan untuk mengisi bahagian halaman yang berulang secara dinamis.
|
||||
|
||||
Kami akan menggunakan pendekatan yang serupa untuk memaparkan senarai transaksi dalam jadual HTML.
|
||||
|
||||
### Tugas
|
||||
|
||||
Tambahkan templat baru dalam HTML `<body>`:
|
||||
|
||||
```html
|
||||
<template id="transaction">
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</template>
|
||||
```
|
||||
|
||||
Templat ini mewakili satu baris jadual, dengan 3 lajur yang ingin kita isi: *tarikh*, *objek* dan *jumlah* transaksi.
|
||||
|
||||
Kemudian, tambahkan sifat `id` ini ke elemen` <tbody> `di dalam templat papan pemuka untuk memudahkan pencarian menggunakan JavaScript:
|
||||
|
||||
```html
|
||||
<tbody id="transactions"></tbody>
|
||||
```
|
||||
|
||||
HTML kami sudah siap, mari beralih ke kod JavaScript dan buat fungsi baru `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;
|
||||
}
|
||||
```
|
||||
|
||||
Fungsi ini melakukan apa yang disiratkan namanya: menggunakan templat yang kita buat sebelumnya, ia membuat baris jadual baru dan mengisi isinya menggunakan data transaksi. Kami akan menggunakannya dalam fungsi `updateDashboard()` kami untuk mengisi jadual:
|
||||
|
||||
```js
|
||||
const transactionsRows = document.createDocumentFragment();
|
||||
for (const transaction of account.transactions) {
|
||||
const transactionRow = createTransactionRow(transaction);
|
||||
transactionsRows.appendChild(transactionRow);
|
||||
}
|
||||
updateElement('transactions', transactionsRows);
|
||||
```
|
||||
|
||||
Di sini kita menggunakan kaedah [`document.createDocumentFragment()`](https://developer.mozilla.org/docs/Web/API/Document/createDocumentFragment) yang membuat serpihan DOM baru yang boleh kita gunakan , sebelum akhirnya melampirkannya ke jadual HTML kami.
|
||||
|
||||
Masih ada satu perkara lagi yang harus kita lakukan sebelum kod ini dapat berfungsi, kerana fungsi `updateElement()` kami pada masa ini hanya menyokong kandungan teks. Mari ubah kodnya sedikit:
|
||||
|
||||
```js
|
||||
function updateElement(id, textOrNode) {
|
||||
const element = document.getElementById(id);
|
||||
element.textContent = ''; // Removes all children
|
||||
element.append(textOrNode);
|
||||
}
|
||||
```
|
||||
|
||||
Kami menggunakan kaedah [`append()`](https://developer.mozilla.org/docs/Web/API/ParentNode/append) kerana ia membolehkan melampirkan teks atau [DOM Nodes] (https : //developer.mozilla.org/docs/Web/API/Node) ke elemen induk, yang sangat sesuai untuk semua kes penggunaan kami.
|
||||
|
||||
Sekiranya anda cuba menggunakan akaun `test` untuk log masuk, kini anda akan melihat senarai transaksi di papan pemuka 🎉.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Cabaran
|
||||
|
||||
Bekerjasama untuk menjadikan halaman papan pemuka kelihatan seperti aplikasi perbankan sebenar. Sekiranya anda sudah menggayakan aplikasi anda, cuba gunakan [pertanyaan media](https://developer.mozilla.org/docs/Web/CSS/Media_Queries) untuk membuat [responsive design](https://developer.mozilla.org/docs/Web/Progressive_web_apps/Responsive/responsive_design_building_blocks) berfungsi dengan baik pada kedua-dua desktop dan peranti mudah alih.
|
||||
|
||||
Berikut adalah contoh halaman papan pemuka yang digayakan:
|
||||
|
||||

|
||||
|
||||
## Kuiz Pasca Kuliah
|
||||
|
||||
[Kuiz Pasca Kuliah](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/46)
|
||||
|
||||
## Tugasan
|
||||
|
||||
[Refaktor dan komen kod anda](assignment.ms.md)
|
@@ -1,336 +0,0 @@
|
||||
# 建立銀行網頁應用程式 Part 3:取得並使用資料
|
||||
|
||||
## 課前測驗
|
||||
|
||||
[課前測驗](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/45?loc=zh_tw)
|
||||
|
||||
### 大綱
|
||||
|
||||
每一個網頁應用程式的核心為*資料*。資料有很多種格式,但它們的目的都是為了顯示使用者需要的資訊。網頁應用程式變得高互動性與複雜,使用者如何取得內容並與之進行互動變成網頁開發重要的一環。
|
||||
|
||||
在這堂課中,我們會了解伺服器是如何非同步地取得資料,並在不重新載入 HTML 的情況下,利用這些資料顯示在網頁上。
|
||||
|
||||
### 開始之前
|
||||
|
||||
你需要先完成系列課程 ── [登入與註冊表單](../../2-forms/translations/README.zh-tw.md)。你還需要安裝 [Node.js](https://nodejs.org) 並[執行伺服器 API](../../api/translations/README.zh-tw.md)。
|
||||
|
||||
你可以測試伺服器是否運作正常,在終端機中輸入指令:
|
||||
|
||||
```sh
|
||||
curl http://localhost:5000/api
|
||||
# -> 會回傳結果 "Bank API v1.0.0"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## AJAX 和取得資料
|
||||
|
||||
傳統的網頁在使用者點擊連結,或是提交表單資料時,重新載入全部的 HTML 頁面來更新網頁內容。每當資料要被更新時,伺服器就需要回傳全新的 HTML 頁面給瀏覽器處理,同時也干涉到使用者正在進行的動作,重新載入的機制也限制了許多互動功能。這種工作流程被稱為*多頁面應用程式(Multi-Page Application)*,簡稱 *MPA*。
|
||||
|
||||

|
||||
|
||||
網頁應用程式變得更加複雜,促使新的技術問世:[AJAX (Asynchronous JavaScript and XML)](https://zh.wikipedia.org/wiki/AJAX)。
|
||||
|
||||
這個技巧允許網頁應用程式使用 JavaScript 非同步地傳遞與接收伺服器的資料,不需要重新載入 HTML 頁面,也反映在更快速的更新速率與更流暢的使用者體驗。在接收伺服器的新資料時,目前的 HTML 頁面可以被 JavaScript 利用 [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model) API 更新。自此之後,這種流程演變成現今的[*單一頁面應用程式(Single-Page Application)*,*SPA*](https://zh.wikipedia.org/wiki/%E5%8D%95%E9%A1%B5%E5%BA%94%E7%94%A8)。
|
||||
|
||||

|
||||
|
||||
在 AJAX 早期,唯一取得資料的 API 為 [`XMLHttpRequest`](https://developer.mozilla.org/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest)。但當代的瀏覽器已經建立出更方便且強大的 [`Fetch` API](https://developer.mozilla.org/docs/Web/API/Fetch_API),它們使用 Promises 物件且更適合應用在 JSON 資料上。
|
||||
|
||||
> 許多當代瀏覽器支援 `Fetch API`,如果你想確認你的網頁應用程式是否運作在舊款的瀏覽器,檢查 [caniuse.com 上的相容性測試](https://caniuse.com/fetch)是一個好方法。
|
||||
|
||||
### 課題
|
||||
|
||||
在[前一堂課程中](../../2-forms/translations/README.zh-tw.md),我們製作出註冊表單來建立新帳戶。現在我們來加入新程式,使用現有的帳戶登入,並取得其相關資料。開啟檔案 `app.js` 並新增函式 `login`:
|
||||
|
||||
```js
|
||||
async function login() {
|
||||
const loginForm = document.getElementById('loginForm')
|
||||
const user = loginForm.user.value;
|
||||
}
|
||||
```
|
||||
|
||||
現在我們利用 `getElementById()` 接收表單元素,並藉由 `loginForm.user.value` 取得輸入框內的使用者名稱。每一個表單控制可以以各自名稱(即 HTML 內的 `name` 屬性)來存取。
|
||||
|
||||
就像我們為註冊帳戶作的事一樣,我們建立另一個函式來執行伺服器請求,但這次是為了取得帳戶資料:
|
||||
|
||||
```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' };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
我們使用 `fetch` API 來向伺服器做非同步資料請求。這次我們不需要添加額外的參數,如網址,我們只詢問資料內容。預設上,`fetch` 建立出 [`GET`](https://developer.mozilla.org/docs/Web/HTTP/Methods/GET) HTTP 請求,即我們想做的事情。
|
||||
|
||||
✅ 函式 `encodeURIComponent()` 可以轉換網址內的特殊字元。如果我們不呼叫這個函式,而是直接將 `user` 這項數值放入網址中,這會發生什麼問題?
|
||||
|
||||
讓我們來更新函式 `login`,使用 `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');
|
||||
}
|
||||
```
|
||||
|
||||
首先,`getAccount` 是一個非同步函式,它利用關鍵字 `await` 等待伺服器的回傳結果。就如其他伺服器請求一樣,我們也必須要處理錯誤的情況。現在我們只加錯誤訊息給這些情況,之後再回過頭解決這些問題。
|
||||
|
||||
接著,我們必須儲存資料,在之後可以輸出成儀表板的資訊。目前變數 `account` 還沒存在,我們建立它的全域變數在檔案最上方:
|
||||
|
||||
```js
|
||||
let account = null;
|
||||
```
|
||||
|
||||
在用戶資料存到變數中後,我們可以使用函式 `navigate()` 從*登入*頁面切換到*儀表板*頁面。
|
||||
|
||||
最後,在登入表單提交時,呼叫函式 `login`。修改 HTML 語法:
|
||||
|
||||
```html
|
||||
<form id="loginForm" action="javascript:login()">
|
||||
```
|
||||
|
||||
測試註冊功能,以及新註冊的帳戶的登入行為是否運作正常。
|
||||
|
||||
在進行下一步驟前,我們還必須完成函式 `register`。在此函式的最下方加入:
|
||||
|
||||
```js
|
||||
account = result;
|
||||
navigate('/dashboard');
|
||||
```
|
||||
|
||||
✅ 你知道在預設上,你只能從*同一個網域(domain)與連接埠(port)*的網頁呼叫伺服器 APIs 嗎?這是瀏覽器強制性的安全機制。但我們的網頁應用程式在 `localhost:3000` 上執行,而伺服器 API 則在 `localhost:5000` 上執行。為什麼這樣能正常運作?利用[跨來源資源共用 (CORS)](https://developer.mozilla.org/docs/Web/HTTP/CORS),只要伺服器添加特殊的標頭檔到網頁回應中,我們就可以處理跨資源的 HTTP 請求,允許特殊的網域進行呼叫。
|
||||
|
||||
> 藉由前往[此課程](https://docs.microsoft.com/learn/modules/use-apis-discover-museum-art/?WT.mc_id=academic-77807-sagibbon)學習更多有關 API 的資訊。
|
||||
|
||||
## 更新 HTML 顯示資料
|
||||
|
||||
現在取得完用戶資料,我們必須更新到現有的 HTML 上。我們已經知道如何接收 DOM 的元素,例子為 `document.getElementById()`。只要你有元素,這邊有一些 API 讓你修改,或是新增子元素上去:
|
||||
|
||||
- 使用 [`textContent`](https://developer.mozilla.org/docs/Web/API/Node/textContent) 屬性,你可以改變元素的文字內容。注意改變此數值會刪除它的所有子元素(若存在的話),並以該文字內容來替換它。同時,這也是個有效的方法來刪去所有的子成員,只要賦予它空字串 `''`。
|
||||
|
||||
- 使用 [`document.createElement()`](https://developer.mozilla.org/docs/Web/API/Document/createElement) 與 [`append()`](https://developer.mozilla.org/docs/Web/API/ParentNode/append) 這兩方法,你可以建立並接上一到多個子元素成員。
|
||||
|
||||
✅ 使用 [`innerHTML`](https://developer.mozilla.org/docs/Web/API/Element/innerHTML) 元素屬性也能改變 HTML 的內容,但這方法要避免使用。這可能會遭遇有關[跨網站指令碼 (XSS)](https://developer.mozilla.org/docs/Glossary/Cross-site_scripting)的攻擊。
|
||||
|
||||
### 課題
|
||||
|
||||
在來到儀表板畫面之前,我們還需要幫*登入*頁面加一件事。目前,如果你試著使用不存在的帳戶進行登入,訊息只會出現在命令欄中,而使用者不會發覺到任何事情改變,也不清楚網頁發生了什麼事。
|
||||
|
||||
我們在登入表單中新增顯示錯誤訊息的地方。最好的地方為登入按鈕 `<button>` 之前:
|
||||
|
||||
```html
|
||||
...
|
||||
<div id="loginError"></div>
|
||||
<button>Login</button>
|
||||
...
|
||||
```
|
||||
|
||||
這個 `<div>` 元素為空的,代表著畫面不會印出任何訊息,直到我們添加內容進去。我們還給了它 `id`,讓 JavaScript 可以容易地存取它。
|
||||
|
||||
回到檔案 `app.js`,建立新的補助函數 `updateElement`:
|
||||
|
||||
```js
|
||||
function updateElement(id, text) {
|
||||
const element = document.getElementById(id);
|
||||
element.textContent = text;
|
||||
}
|
||||
```
|
||||
|
||||
這條就很直觀:給定元素的 *id* 與 *text*,它會更新 DOM 元素內符合 `id` 條件的文字內容。我們也使用這個方法到前面 `login` 函式的錯誤訊息中:
|
||||
|
||||
```js
|
||||
if (data.error) {
|
||||
return updateElement('loginError', data.error);
|
||||
}
|
||||
```
|
||||
|
||||
現在,試著以不合法的帳戶進行登入,你應該能看到像這樣的畫面:
|
||||
|
||||

|
||||
|
||||
現在我們印出錯誤訊息,但螢幕報讀器並沒有做任何報讀。為了讓被動態加入的文字能被螢幕報讀器閱讀出來,我們需要使用 [Live Region](https://developer.mozilla.org/docs/Web/Accessibility/ARIA/ARIA_Live_Regions)。這邊我們使用一種 Live Region 的類型 alert:
|
||||
|
||||
```html
|
||||
<div id="loginError" role="alert"></div>
|
||||
```
|
||||
|
||||
建立同樣的行為到函式 `register` 的錯誤訊息當中,也別忘了更新你的 HTML。
|
||||
|
||||
## 在儀表板顯示資訊
|
||||
|
||||
使用同樣的技巧,我們需要將帳戶資訊印在儀表板頁面中。
|
||||
|
||||
這是從伺服器接收到的帳戶資料物件:
|
||||
|
||||
```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 }
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
> 筆記:為了讓開發更加的容易,你可以使用已經存在資料的帳戶 `test`。
|
||||
|
||||
### 課題
|
||||
|
||||
我們開始置換掉 HTML 檔內的 "Balance" 區域,加入放置區:
|
||||
|
||||
```html
|
||||
<section>
|
||||
Balance: <span id="balance"></span><span id="currency"></span>
|
||||
</section>
|
||||
```
|
||||
|
||||
我們還需要在下方新增區域來顯示帳戶資訊:
|
||||
|
||||
```html
|
||||
<h2 id="description"></h2>
|
||||
```
|
||||
|
||||
✅ 表示帳戶資訊的函式剛好為在內容的標題處,我們可以將它作為語義化的標頭。學習更多關於[標頭結構](https://www.nomensa.com/blog/2017/how-structure-headings-web-accessibility),它對於網頁親和力格外重要,也明顯地表達出頁面的標頭位置。
|
||||
|
||||
接著,我們在 `app.js` 檔案中加入新的函式來為放置區新增內容:
|
||||
|
||||
```js
|
||||
function updateDashboard() {
|
||||
if (!account) {
|
||||
return navigate('/login');
|
||||
}
|
||||
|
||||
updateElement('description', account.description);
|
||||
updateElement('balance', account.balance.toFixed(2));
|
||||
updateElement('currency', account.currency);
|
||||
}
|
||||
```
|
||||
|
||||
首先,我們需要先檢查帳戶的資料。使用我們之前建立的函式 `updateElement()` 來更新 HTML 檔。
|
||||
|
||||
> 為了讓帳戶餘額漂亮地呈現,我們使用 [`toFixed(2)`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed) 這個方法,強迫數值只顯示到小數點第二位。
|
||||
|
||||
現在,每當儀表板被載入時,我們就需要呼叫函式 `updateDashboard()`。如果你已經完成[課程一的作業](../../1-template-route/translations/assignment.zh-tw.md),就不需要額外做處理,不然你可以使用接下來的設定。
|
||||
|
||||
加入這段程式碼到函式 `updateRoute()` 的下方:
|
||||
|
||||
```js
|
||||
if (typeof route.init === 'function') {
|
||||
route.init();
|
||||
}
|
||||
```
|
||||
|
||||
並更新路由定義:
|
||||
|
||||
```js
|
||||
const routes = {
|
||||
'/login': { templateId: 'login' },
|
||||
'/dashboard': { templateId: 'dashboard', init: updateDashboard }
|
||||
};
|
||||
```
|
||||
|
||||
做完這些更動後,當儀表板要被呈現時,函式 `updateDashboard() 就會被呼叫。在你登入後就能看到帳戶的描述、餘額與交易狀況。
|
||||
|
||||
## 利用 HTML 模板動態建立表格列
|
||||
|
||||
在[第一堂課](../../1-template-route/translations/README.zh-tw.md)中,我們使用 HTML 模板與方法 [`appendChild()`](https://developer.mozilla.org/docs/Web/API/Node/appendChild) 來做出應用程式內的轉換。模板還能執行更小規模的行為,動態地改變一部份的頁面
|
||||
|
||||
我們使用類似的方式來顯示 HTML 表格中的交易清單。
|
||||
|
||||
### 課題
|
||||
|
||||
加入新的模板到 HTML 的 `<body>` 中:
|
||||
|
||||
```html
|
||||
<template id="transaction">
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</template>
|
||||
```
|
||||
|
||||
這個模板表示單一條的表格列,其中包含了三格欄位:交易的*日期*、*物件* 與 *金額*。
|
||||
|
||||
接著,加入 `id` 屬性到模板的表格 `<tbody>` 元素中,讓 JavaScript 能更容易地取得:
|
||||
|
||||
```html
|
||||
<tbody id="transactions"></tbody>
|
||||
```
|
||||
|
||||
當我們的 HTML 準備好時,我們切換到 JavaScript 檔案中,加入新函式 `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;
|
||||
}
|
||||
```
|
||||
|
||||
這個函式做就如它名字的功能:藉由剛建立的模板,建立出新的表格列並填入交易明細的資料。我們會在函式 `updateDashboard()` 中,利用它來更新表格:
|
||||
|
||||
```js
|
||||
const transactionsRows = document.createDocumentFragment();
|
||||
for (const transaction of account.transactions) {
|
||||
const transactionRow = createTransactionRow(transaction);
|
||||
transactionsRows.appendChild(transactionRow);
|
||||
}
|
||||
updateElement('transactions', transactionsRows);
|
||||
```
|
||||
|
||||
這裡我們使用了方法 [`document.createDocumentFragment()`](https://developer.mozilla.org/docs/Web/API/Document/createDocumentFragment),建立新的 DOM 分段,再接到我們的 HTML 表格中。
|
||||
|
||||
我們還需要做一件事才能讓程式運作正常,目前函式 `updateElement()` 只能接受文字類型的內容。我們稍微修改一下程式碼:
|
||||
|
||||
```js
|
||||
function updateElement(id, textOrNode) {
|
||||
const element = document.getElementById(id);
|
||||
element.textContent = ''; // Removes all children
|
||||
element.append(textOrNode);
|
||||
}
|
||||
```
|
||||
|
||||
我們使用方法 [`append()`](https://developer.mozilla.org/docs/Web/API/ParentNode/append),它能連接文字或者是 [DOM 節點](https://developer.mozilla.org/docs/Web/API/Node)到父元素中,正好滿足我們的需求。
|
||||
|
||||
試著以 `test` 帳戶來登入,你應該能看到儀表板顯示出交易明細了 🎉。
|
||||
|
||||
---
|
||||
|
||||
## 🚀 挑戰
|
||||
|
||||
花功夫讓儀表板頁面看起來像是正規的銀行界面。如果你已經為你的應用程式做好造型,你可以試試 [media queries](https://developer.mozilla.org/docs/Web/CSS/Media_Queries) 來建立出[回應式網頁設計](https://developer.mozilla.org/docs/Web/Progressive_web_apps/Responsive/responsive_design_building_blocks),它能完美地呈現在電腦或是行動裝置上。
|
||||
|
||||
這邊有造型過後的儀表板例子:
|
||||
|
||||

|
||||
|
||||
## 課後測驗
|
||||
|
||||
[課後測驗](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/46?loc=zh_tw)
|
||||
|
||||
## 作業
|
||||
|
||||
[重構並註解你的程式碼](assignment.zh-tw.md)
|
@@ -1,15 +0,0 @@
|
||||
# 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. |
|
@@ -1,15 +0,0 @@
|
||||
# Refactorisez et commentez votre code
|
||||
|
||||
## Instructions
|
||||
|
||||
Au fur et à mesure que votre base de code se développe, il est important de refactorisez votre code fréquemment pour qu'il reste lisible et maintenable dans le temps. Ajoutez des commentaires et refactorisez votre `app.js` pour améliorer la qualité du code :
|
||||
|
||||
- Extrayez des constantes, telles que l'URL de base de l'API du serveur.
|
||||
- Factorisez tout code similaire : par exemple, vous pouvez créer une fonction `sendRequest()` pour regrouper le code utilisé dans les fonctions `createAccount()` et `getAccount()`.
|
||||
- Réorganisez le code pour le rendre plus facile à lire, et ajoutez des commentaires.
|
||||
|
||||
## Rubrique
|
||||
|
||||
| Critères | Exemplaire | Adéquat | Besoin d'amélioration |
|
||||
| -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
|
||||
| | Le code est commenté, bien organisé en différentes sections et facile à lire. Les constantes sont extraites et une fonction `sendRequest()` a été créée. | Le code est propre mais peut encore être amélioré avec plus de commentaires, d'extraction de constantes ou de factorisation.| Le code est désordonné, non commenté, les constantes ne sont pas extraites et le code n'est pas factorisé.|
|
@@ -1,15 +0,0 @@
|
||||
# अपना कोड रिफैक्टर और टिप्पणी करें
|
||||
|
||||
## अनुदेश
|
||||
|
||||
जैसे-जैसे आपका कोडबेस बढ़ता है, समय के साथ-साथ पठनीय और बनाए रखने के लिए अपने कोड को बार-बार रिफलेक्टर करना महत्वपूर्ण है। टिप्पणी जोड़ें और कोड की गुणवत्ता में सुधार करने के लिए अपने `app.js` को फिर से फ़िल्टर करें:
|
||||
|
||||
- सर्वर API बेस URL की तरह, स्थिरांक निकालें
|
||||
- समान कोड को फैक्टराइज़ करें: उदाहरण के लिए आप दोनों `createAccount()` और `getAccount()` में उपयोग किए गए कोड को फिर से इकट्ठा करने के लिए एक `sendRequest()` फ़ंक्शन बना सकते हैं।
|
||||
- कोड को पढ़ने, और टिप्पणी जोड़ने में आसान बनाने के लिए इसे पुनर्गठित करें
|
||||
|
||||
## शीर्ष
|
||||
|
||||
| मानदंड | उदाहरणात्मक | पर्याप्त | सुधार की जरूरत |
|
||||
| ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
|
||||
| | कोड टिप्पणी की गई है, विभिन्न वर्गों में सुव्यवस्थित और पढ़ने में आसान है। स्थिरांक निकाले जाते हैं और एक कारक `sendRequest()` फ़ंक्शन बनाया गया है. | कोड साफ है लेकिन फिर भी अधिक टिप्पणियों, निरंतर निष्कर्षण या कारक के साथ सुधार किया जा सकता है. | कोड गड़बड़ है, टिप्पणी नहीं की गई है, स्थिरांक नहीं निकाले गए हैं और कोड कारक नहीं है. |
|
@@ -1,15 +0,0 @@
|
||||
# Refattorizzare e commentare il codice
|
||||
|
||||
## Istruzioni
|
||||
|
||||
Man mano che la base del codice cresce, è importante rifattorizzare il codice frequentemente per mantenerlo leggibile e gestibile nel tempo. Aggiungere commenti e rifattorizzare la propria `app.js` per migliorare la qualità del codice:
|
||||
|
||||
- Estrarre costanti, come l'URL di base dell'API del server
|
||||
- Fattorizzare codice simile: ad esempio si può creare una funzione `sendRequest()` per raggruppare il codice utilizzato sia in `createAccount()` che in `getAccount()`
|
||||
- Riorganizzare il codice per renderlo più facile da leggere e aggiungere commenti
|
||||
|
||||
## Rubrica
|
||||
|
||||
| Criteri | Ottimo | Adeguato | Necessita miglioramento |
|
||||
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
|
||||
| | Il codice è commentato, ben organizzato in diverse sezioni e di facile lettura. Le costanti vengono estratte ed è stata creata una funzione `sendRequest()` fattorizzata. | Il codice è pulito ma può ancora essere migliorato con più commenti, estrazione di costanti o fattorizzazione. | Il codice è disordinato, non commentato, le costanti non vengono estratte e il codice non è fattorizzato |
|
@@ -1,15 +0,0 @@
|
||||
# コードのリファクタとコメント
|
||||
|
||||
## 説明書
|
||||
|
||||
コードベースが大きくなってくると、読みやすく保守性の高いコードを維持するために、頻繁にリファクタリングを行うことが重要になってきます。コメントを追加して `app.js` をリファクタリングし、コードの品質を向上させましょう。
|
||||
|
||||
- サーバー API のベース URL のような定数を抽出します
|
||||
- 類似したコードの因数分解: 例えば、`sendRequest()` 関数を作成して `createAccount()` と `getAccount()` の両方で使用するコードを再グループ化することができます
|
||||
- 読みやすいようにコードを再編成し、コメントを追加します
|
||||
|
||||
## ルーブリック
|
||||
|
||||
| 基準 | 模範的な例 | 適切な | 改善が必要 |
|
||||
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
|
||||
| | コードはコメントされており、さまざまなセクションでよく整理されており、読みやすくなっています。定数が抽出され、因数分解された `sendRequest()` 関数が作成されています。 | コードはクリーンですが、より多くのコメント、定数抽出、因数分解などで改善することができます。 | コードは乱雑で、コメントされておらず、定数は抽出されておらず、コードは因数分解されていません。 |
|
@@ -1,15 +0,0 @@
|
||||
# 코드 리팩터링 및 주석
|
||||
|
||||
## 설명
|
||||
|
||||
코드베이스가 커짐에 따라 코드를 자주 리팩터링하여 시간이 지남에 따라 가독성과 유지 관리가 가능하도록 유지하는 것이 중요합니다. 주석을 추가하고 `app.js` 를 리팩터링하여 코드 품질을 개선합니다.
|
||||
|
||||
- 서버 API 기본 URL과 같은 상수 추출
|
||||
- 유사한 코드를 분해합니다. 예를 들어 `createAccount()` 및 `getAccount()` 모두에서 사용되는 코드를 다시 그룹화하는 `sendRequest()` 함수를 만들 수 있습니다.
|
||||
- 읽기 쉽도록 코드 재구성 및 주석 추가
|
||||
|
||||
## 평가 기준
|
||||
|
||||
기준 | 모범 답안 | 적당한 답안 | 개선이 필요한 답안
|
||||
--- | --- | --- | ---
|
||||
| 코드 주석이 잘 짜여진 여러개의 섹션으로 나뉘며 읽기 쉬운 경우. 그리고 상수들이 추출되고 잘 쪼개진 `sendRequest()` 함수를 생성한 경우 | 코드는 깔끔하지만 더 많은 주석, 지속적인 추출 또는 분해를 통해 개선될 수 있는 경우 | 코드가 지저분하고 주석도 잘 작성하지 않았으며 상수 또한 추출되지 않고 코드도 분해되지 않은 경우
|
@@ -1,15 +0,0 @@
|
||||
# Refaktor dan komen kod anda
|
||||
|
||||
## Arahan
|
||||
|
||||
Apabila pangkalan kod anda berkembang, penting untuk kerap mengubah kod anda agar tetap dapat dibaca dan dikekalkan dari masa ke masa. Tambahkan komen dan ubah semula `app.js` anda untuk meningkatkan kualiti kod:
|
||||
|
||||
- Ekstrak pemalar, seperti URL asas API pelayan
|
||||
- Faktorkan kod yang serupa: misalnya, anda dapat membuat fungsi `sendRequest()` untuk mengumpulkan kembali kod yang digunakan dalam `createAccount()` dan `getAccount()`
|
||||
- Susun semula kod untuk memudahkan membaca, dan menambah komen
|
||||
|
||||
## Rubrik
|
||||
|
||||
| Kriteria | Contoh | Mencukupi | Usaha Lagi |
|
||||
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
|
||||
| | Kod dikomentari, disusun dengan baik dalam pelbagai bahagian dan senang dibaca Pemalar diekstrak dan fungsi `sendRequest()` yang difaktorkan telah dibuat. | Kodnya bersih tetapi masih boleh diperbaiki dengan lebih banyak komen, pengekstrakan berterusan atau pemfaktoran. | Kod tidak kemas, tidak dikomentari, pemalar tidak diekstrak dan kod tidak difaktorkan. |
|
@@ -1,15 +0,0 @@
|
||||
# Refactoreer en becommentarieer uw code
|
||||
|
||||
## Instructies
|
||||
|
||||
Naarmate uw codebase groeit, is het belangrijk om uw code regelmatig te refactoren om deze in de loop van de tijd leesbaar en onderhoudbaar te houden. Voeg opmerkingen toe en refactoreer uw `app.js` om de kwaliteit van de code te verbeteren:
|
||||
|
||||
- Extraheer constanten, zoals de basis-URL van de server-API
|
||||
- Factoriseer vergelijkbare code: u kunt bijvoorbeeld een `sendRequest()` functie maken om de code die wordt gebruikt in zowel `createAccount()` als `getAccount()` groeperen
|
||||
- Reorganiseer de code zodat deze gemakkelijker leesbaar is en voeg opmerkingen toe
|
||||
|
||||
## Rubriek
|
||||
|
||||
| Criteria | Voorbeeldig | Voldoende | Moet worden verbeterd |
|
||||
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
|
||||
| | Code is becommentarieerd, goed georganiseerd in verschillende secties en gemakkelijk te lezen. Constanten worden geëxtraheerd en er is een gefactoriseerde `sendRequest()`-functie gemaakt. | Code is schoon, maar kan nog worden verbeterd met meer opmerkingen, constante extractie of factorisatie. | Code is rommelig, er wordt geen commentaar op gegeven, constanten worden niet geëxtraheerd en code wordt niet in factoren verwerkt. |
|
@@ -1,15 +0,0 @@
|
||||
# 重構並註解你的程式碼
|
||||
|
||||
## 簡介
|
||||
|
||||
正當你的程式碼越來越多時,頻繁地重構你的程式,讓程式碼容易去閱讀與維護變得十分重要。加入一些註解並重構檔案 `app.js` 來增進檔案的品質:
|
||||
|
||||
- 取出常數,好比說伺服器 API 的根網址
|
||||
- 重構相同的程式碼:舉例來說,你可以建立函式 `sendRequest()` 來合併 `createAccount()` 與 `getAccount()`。
|
||||
- 重新編排你的程式和加入註解,讓它更容易地閱讀。
|
||||
|
||||
## 學習評量
|
||||
|
||||
| 作業內容 | 優良 | 普通 | 待改進 |
|
||||
| -------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------- |
|
||||
| | 程式碼有做註解,分塊地整理好。常數有被取出來且函式 `sendRequest()` 已設定完成 | 程式碼有做過處理,但仍可以藉由加入註解、排版與重構來增進品質 | 程式碼很雜亂,缺乏註解,常數與函式並沒有做規劃 |
|
@@ -1,278 +0,0 @@
|
||||
# Conceptos de gestión del estado
|
||||
|
||||
## [Pre-lecture prueba](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/47)
|
||||
|
||||
### Introducción
|
||||
|
||||
A medida que una aplicación web crece, mantener un seguimiento limpio de todos los flujos de datos se convierte en un desafío. Qué código obtiene los datos, qué página los consume, dónde y cuándo debe actualizarse, es fácil terminar con un código desordenado y difícil de mantener. Esto es especialmente cierto cuando necesita compartir datos entre diferentes páginas de su aplicación, por ejemplo, los datos del usuario. El concepto de *gestión estatal* siempre ha existido en todo tipo de programas, pero a medida que las aplicaciones web siguen creciendo en complejidad, ahora es un punto clave en el que pensar durante el desarrollo.
|
||||
|
||||
En esta parte final, revisaremos la aplicación que creamos para repensar cómo se administra el estado, lo que permite la compatibilidad con la actualización del navegador en cualquier momento y la persistencia de datos en las sesiones de los usuarios.
|
||||
|
||||
### Requisito previo
|
||||
|
||||
Debe haber completado la parte [obtención de datos](../3-data/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 administrar los datos de la cuenta.
|
||||
|
||||
Puede probar que el servidor está funcionando correctamente ejecutando este comando en una terminal:
|
||||
|
||||
|
||||
```sh
|
||||
curl http://localhost:5000/api
|
||||
# -> debe devolver "API bancaria v1.0.0" como resultado
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Repensar la gestión del estado
|
||||
|
||||
En la [lección anterior](../3-data/README.md), empezamos con un concepto básico de estado en nuestra aplicación con la variable global `account` que contiene los datos bancarios del usuario actualmente conectado. Sin embargo, nuestra implementación actual tiene algunas fallas. Intente actualizar la página cuando esté en el panel. Pero ¿qué pasa?
|
||||
|
||||
Hay 3 problemas con el código actual:
|
||||
|
||||
- El estado no persiste, ya que una actualización del navegador lo lleva de regreso a la página de inicio de sesión.
|
||||
- Hay múltiples funciones que modifican el estado. A medida que la aplicación crece, puede dificultar el seguimiento de los cambios y es fácil olvidarse de actualizar uno.
|
||||
- El estado no se limpia, cuando hace clic en *Cerrar sesión*, los datos de la cuenta siguen ahí aunque esté en la página de inicio de sesión.
|
||||
|
||||
Podríamos actualizar nuestro código para abordar estos problemas uno por uno, pero crearía más duplicación de código y haría que la aplicación sea más compleja y difícil de mantener. O podríamos hacer una pausa por unos minutos y repensar nuestra estrategia.
|
||||
|
||||
> ¿Qué problemas estamos realmente tratando de resolver aquí?
|
||||
|
||||
La [gestión del estado](https://en.wikipedia.org/wiki/State_management) se trata de encontrar un buen enfoque para resolver estos dos problemas particulares:
|
||||
|
||||
- ¿Cómo mantener los flujos de datos en una aplicación fáciles de entender?
|
||||
- ¿Cómo mantener los datos de estado siempre sincronizados con la interfaz de usuario (y viceversa)?
|
||||
|
||||
Una vez que se haya ocupado de esto, es posible que cualquier otro problema que pueda tener ya esté solucionado o que sea más fácil de solucionar. Hay muchos enfoques posibles para resolver estos problemas, pero optaremos por una solución común que consiste en **centralizar los datos y las formas de cambiarlos**. Los flujos de datos serían así:
|
||||
|
||||

|
||||
|
||||
> No cubriremos aquí la parte en la que los datos activan automáticamente la actualización de la vista, ya que está vinculada a conceptos más avanzados de [Programación reactiva](https://en.wikipedia.org/wiki/Reactive_programming). Es un buen tema de seguimiento si estás preparado para una inmersión profunda.
|
||||
|
||||
✅ Hay muchas bibliotecas con diferentes enfoques para la administración del estado, [Redux](https://redux.js.org) es una opción popular. Eche un vistazo a los conceptos y patrones utilizados, ya que a menudo es una buena manera de aprender qué problemas potenciales puede enfrentar en aplicaciones web grandes y cómo se pueden resolver.
|
||||
|
||||
### Tarea
|
||||
|
||||
Comenzaremos con un poco de refactorización. Reemplace la declaración `account`:
|
||||
|
||||
```js
|
||||
let account = null;
|
||||
```
|
||||
|
||||
Con:
|
||||
|
||||
```js
|
||||
let state = {
|
||||
account: null
|
||||
};
|
||||
```
|
||||
|
||||
La idea es *centralizar* todos los datos de nuestra aplicación en un solo objeto de estado. Solo tenemos "cuenta" por ahora en el estado, por lo que no cambia mucho, pero crea un camino fácil para las evoluciones.
|
||||
|
||||
También tenemos que actualizar las funciones usándolo. En las funciones `register()` y `login()`, reemplace `account=...` con `state.account=...`;
|
||||
|
||||
En la parte superior de la función `updateDashboard()`, agregue esta línea:
|
||||
|
||||
```js
|
||||
const account = state.account;
|
||||
```
|
||||
|
||||
Esta refactorización por sí sola no trajo muchas mejoras, pero la idea era sentar las bases para los próximos cambios.
|
||||
|
||||
## Seguimiento de cambios de datos
|
||||
|
||||
Ahora que hemos implementado el objeto `state` para almacenar nuestros datos, el siguiente paso es centralizar las actualizaciones. El objetivo es facilitar el seguimiento de los cambios y cuándo ocurren.
|
||||
|
||||
Para evitar que se realicen cambios en el objeto `state`, también es una buena práctica considerarlo [*inmutable*](https://en.wikipedia.org/wiki/Immutable_object), lo que significa que no se puede modificar en absoluto. También significa que debe crear un nuevo objeto de estado si desea cambiar algo en él. Al hacer esto, crea una protección contra [efectos secundarios](https://en.wikipedia.org/wiki/Side_effect_(computer_science)) potencialmente no deseados y abre posibilidades para nuevas funciones en su aplicación, como implementar deshacer / rehacer, al mismo tiempo que facilita la depuración. Por ejemplo, puede registrar todos los cambios realizados en el estado y mantener un historial de los cambios para comprender el origen de un error.
|
||||
|
||||
En JavaScript, puede usar [`Object.freeze()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) para crear una versión inmutable de un objeto. Si intenta realizar cambios en un objeto inmutable, se generará una excepción.
|
||||
|
||||
✅ ¿Conoce la diferencia entre un objeto *superficial* y un objeto inmutable *profundo*? Puede leer sobre esto [aquí](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze#What_is_shallow_freeze).
|
||||
|
||||
### Tarea
|
||||
|
||||
Creemos una nueva función `updateState()`:
|
||||
|
||||
```js
|
||||
function updateState(property, newData) {
|
||||
state = Object.freeze({
|
||||
...state,
|
||||
[property]: newData
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
En esta función, estamos creando un nuevo objeto de estado y copiamos datos del estado anterior usando el operador [*spread (`...`)*](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/Spread_syntax#Spread_in_object_literals). Luego anulamos una propiedad particular del objeto de estado con los nuevos datos usando la [notación de corchetes](https://developer.mozilla.org/docs/Web/JavaScript/Guide/Working_with_Objects#Objects_and_properties) `[property]` para asignación. Finalmente, bloqueamos el objeto para evitar modificaciones usando `Object.freeze()`. Solo tenemos la propiedad `account` almacenada en el estado por ahora, pero con este enfoque puede agregar tantas propiedades como necesite en el estado.
|
||||
|
||||
También actualizaremos la inicialización del `estado` para asegurarnos de que el estado inicial también esté congelado:
|
||||
|
||||
```js
|
||||
let state = Object.freeze({
|
||||
account: null
|
||||
});
|
||||
```
|
||||
|
||||
Después de eso, actualice la función `register` reemplazando la asignación `state.account = result;` con:
|
||||
|
||||
```js
|
||||
updateState('account', result);
|
||||
```
|
||||
|
||||
Haz lo mismo con la función `login`, reemplazando `state.account = data;` con:
|
||||
|
||||
```js
|
||||
updateState('account', data);
|
||||
```
|
||||
|
||||
Ahora aprovecharemos la oportunidad para solucionar el problema de que los datos de la cuenta no se borran cuando el usuario hace clic en *Cerrar sesión*.
|
||||
|
||||
Cree una nueva función `logout()`:
|
||||
|
||||
```js
|
||||
function logout() {
|
||||
updateState('account', null);
|
||||
navigate('/login');
|
||||
}
|
||||
```
|
||||
|
||||
En `updateDashboard()`, reemplace la redirección `return navigate('/login');` por `return logout()`;
|
||||
|
||||
Intente registrar una nueva cuenta, cierre la sesión y vuelva a iniciarla para comprobar que todo sigue funcionando correctamente.
|
||||
|
||||
> Consejo: puede echar un vistazo a todos los cambios de estado agregando `console.log(state)` en la parte inferior de `updateState()` y abriendo la consola en las herramientas de desarrollo de su navegador.
|
||||
|
||||
## Persiste el estado
|
||||
|
||||
La mayoría de las aplicaciones web necesitan conservar los datos para poder funcionar correctamente. Todos los datos críticos generalmente se almacenan en una base de datos y se accede a ellos a través de una API de servidor, como los datos de la cuenta de usuario en nuestro caso. Pero a veces, también es interesante conservar algunos datos en la aplicación cliente que se ejecuta en su navegador, para una mejor experiencia de usuario o para mejorar el rendimiento de carga.
|
||||
|
||||
Cuando desee conservar los datos en su navegador, hay algunas preguntas importantes que debe hacerse:
|
||||
|
||||
- *¿Son los datos confidenciales?* Debe evitar almacenar datos confidenciales en el cliente, como contraseñas de usuario.
|
||||
- *¿Por cuánto tiempo necesita conservar estos datos?* ¿Planea acceder a estos datos solo para la sesión actual o desea que se almacenen para siempre?
|
||||
|
||||
Hay varias formas de almacenar información dentro de una aplicación web, dependiendo de lo que desee lograr. Por ejemplo, puede utilizar las URL para almacenar una consulta de búsqueda y hacer que se pueda compartir entre los usuarios. También puede utilizar [cookies HTTP](https://developer.mozilla.org/docs/Web/HTTP/Cookies) si los datos deben compartirse con el servidor, como [autenticación](https://en.wikipedia.org/wiki/Authentication) de la información.
|
||||
|
||||
Otra opción es utilizar una de las muchas API del navegador para almacenar datos. Dos de ellos son particularmente interesantes:
|
||||
|
||||
- [`localStorage`](https://developer.mozilla.org/docs/Web/API/Window/localStorage): un [almacén de claves / valores](https://en.wikipedia.org/wiki/Key%E2%80%93value_database) que permite conservar datos específicos del sitio web actual en diferentes sesiones. Los datos guardados en él nunca caducan.
|
||||
- [`sessionStorage`](https://developer.mozilla.org/docs/Web/API/Window/sessionStorage): este funciona igual que `localStorage` excepto que los datos almacenados en él se borran cuando finaliza la sesión (cuando se cierra el navegador).
|
||||
|
||||
Tenga en cuenta que estas dos API solo permiten almacenar [cadenas](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String). Si desea almacenar objetos complejos, deberá serializarlos al formato [JSON](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/JSON) usando [`JSON.stringify()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify).
|
||||
|
||||
✅ Si desea crear una aplicación web que no funcione con un servidor, también es posible crear una base de datos en el cliente usando la [API de IndexedDB](https://developer.mozilla.org/docs/Web/API/IndexedDB_API). Este está reservado para casos de uso avanzados o si necesita almacenar una cantidad significativa de datos, ya que es más complejo de usar.
|
||||
|
||||
### Tarea
|
||||
|
||||
Queremos que nuestros usuarios permanezcan conectados hasta que hagan clic explícitamente en el botón *Cerrar sesión*, por lo que usaremos `localStorage` para almacenar los datos de la cuenta. Primero, definamos una clave que usaremos para almacenar nuestros datos.
|
||||
|
||||
```js
|
||||
const storageKey = 'savedAccount';
|
||||
```
|
||||
|
||||
Luego agregue esta línea al final de la función `updateState()`:
|
||||
|
||||
```js
|
||||
localStorage.setItem(storageKey, JSON.stringify(state.account));
|
||||
```
|
||||
|
||||
Con esto, los datos de la cuenta de usuario serán persistentes y siempre actualizados ya que centralizamos previamente todas nuestras actualizaciones de estado. Aquí es donde comenzamos a beneficiarnos de todas nuestras refactorizaciones anteriores 🙂.
|
||||
|
||||
A medida que se guardan los datos, también tenemos que encargarnos de restaurarlos cuando se carga la aplicación. Ya que comenzaremos a tener más código de inicialización, puede ser una buena idea crear una nueva función `init`, que también incluya nuestro código anterior al final de `app.js`:
|
||||
|
||||
```js
|
||||
function init() {
|
||||
const savedAccount = localStorage.getItem(storageKey);
|
||||
if (savedAccount) {
|
||||
updateState('account', JSON.parse(savedAccount));
|
||||
}
|
||||
|
||||
// Nuestro código de inicialización anterior
|
||||
window.onpopstate = () => updateRoute();
|
||||
updateRoute();
|
||||
}
|
||||
|
||||
init();
|
||||
```
|
||||
|
||||
Aquí recuperamos los datos guardados y, si hay alguno, actualizamos el estado en consecuencia. Es importante hacer esto *antes* de actualizar la ruta, ya que puede haber un código que dependa del estado durante la actualización de la página.
|
||||
|
||||
También podemos hacer que la página *Panel de control* sea nuestra página predeterminada de la aplicación, ya que ahora estamos conservando los datos de la cuenta. Si no se encuentran datos, el panel se encarga de redirigir a la página *Iniciar sesión* de todos modos. En `updateRoute()`, reemplace el respaldo `return navigate('/login');` con `return navigate('dashboard');`.
|
||||
|
||||
Ahora inicie sesión en la aplicación e intente actualizar la página, debe permanecer en el tablero. Con esa actualización nos hemos ocupado de todos nuestros problemas iniciales ...
|
||||
|
||||
## Actualizar los datos
|
||||
|
||||
... Pero también podríamos haber creado uno nuevo. Oups.
|
||||
|
||||
Vaya al panel de control con la cuenta `test`, luego ejecute este comando en una terminal para crear una nueva transacción:
|
||||
|
||||
```sh
|
||||
curl --request POST \
|
||||
--header "Content-Type: application/json" \
|
||||
--data "{ \"date\": \"2020-07-24\", \"object\": \"Bought book\", \"amount\": -20 }" \
|
||||
http://localhost:5000/api/accounts/test/transactions
|
||||
```
|
||||
|
||||
Intente actualizar la página del panel en el navegador ahora. ¿Qué ocurre? ¿Ves la nueva transacción?
|
||||
|
||||
El estado se conserva indefinidamente gracias al `localStorage`, pero eso también significa que nunca se actualiza hasta que cierre la sesión de la aplicación y vuelva a iniciarla.
|
||||
|
||||
Una posible estrategia para solucionarlo es volver a cargar los datos de la cuenta cada vez que se carga el panel, para evitar que se atasquen los datos.
|
||||
|
||||
### Tarea
|
||||
|
||||
Cree una nueva función `updateAccountData`:
|
||||
|
||||
```js
|
||||
async function updateAccountData() {
|
||||
const account = state.account;
|
||||
if (!account) {
|
||||
return logout();
|
||||
}
|
||||
|
||||
const data = await getAccount(account.user);
|
||||
if (data.error) {
|
||||
return logout();
|
||||
}
|
||||
|
||||
updateState('account', data);
|
||||
}
|
||||
```
|
||||
|
||||
Este método comprueba que estamos conectados actualmente y luego vuelve a cargar los datos de la cuenta desde el servidor.
|
||||
|
||||
Cree otro nombre de función `refresh`:
|
||||
|
||||
```js
|
||||
async function refresh() {
|
||||
await updateAccountData();
|
||||
updateDashboard();
|
||||
}
|
||||
```
|
||||
|
||||
Éste actualiza los datos de la cuenta, luego se encarga de actualizar el HTML de la página del tablero. Es lo que necesitamos llamar cuando se carga la ruta del tablero. Actualice la definición de ruta con:
|
||||
|
||||
```js
|
||||
const routes = {
|
||||
'/login': { templateId: 'login' },
|
||||
'/dashboard': { templateId: 'dashboard', init: refresh }
|
||||
};
|
||||
```
|
||||
|
||||
Intente volver a cargar el panel ahora, debería mostrar los datos de la cuenta actualizados.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Desafío
|
||||
|
||||
Ahora que recargamos los datos de la cuenta cada vez que se carga el panel, ¿cree que aún necesitamos conservar *todos los datos de la cuenta*?
|
||||
|
||||
Intente trabajar juntos para cambiar lo que se guarda y carga desde `localStorage` para incluir solo lo que es absolutamente necesario para que la aplicación funcione.
|
||||
|
||||
## [Post-lecture prueba](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/48)
|
||||
|
||||
## Asignación
|
||||
|
||||
[Implementar el cuadro de diálogo "Agregar transacción"](assignment.es.md)
|
||||
|
||||
Aquí hay un ejemplo de resultado después de completar la tarea:
|
||||
|
||||

|
@@ -1,281 +0,0 @@
|
||||
# Créer une application bancaire Partie 4: Concepts de gestion d'état
|
||||
|
||||
## Quiz préalable
|
||||
|
||||
[Quiz préalable](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/47?loc=fr)
|
||||
|
||||
### Introduction
|
||||
|
||||
Au fur et à mesure qu’une application Web se développe, il devient difficile de suivre tous les flux de données. Quel code obtient les données, quelle page les consomme, où et quand doit-il être mis à jour... il est facile de se retrouver avec un code désordonné et difficile à maintenir. Cela est particulièrement vrai lorsque vous devez partager des données entre différentes pages de votre application, par exemple des données utilisateur. Le concept de *gestion de l’état* a toujours existé dans toutes sortes de programmes, mais comme les applications Web ne cessent de croître en complexité, c’est maintenant un point clé à prendre en compte pendant le développement.
|
||||
|
||||
Dans cette dernière partie, nous examinerons l’application que nous avons créée pour repenser la façon dont l’état est géré, permettant la prise en charge de l’actualisation du navigateur à tout moment et la persistance des données entre les sessions utilisateur.
|
||||
|
||||
### Prérequis
|
||||
|
||||
Vous devez avoir terminé la [récupération des données](../../3-data/translations/README.fr.md) de l’application web pour cette leçon. Vous devez également installer [Node.js](https://nodejs.org) et [exécuter l’API du serveur](../../api/translations/README.fr.md) localement afin que vous puissiez gérer les données du compte.
|
||||
|
||||
Vous pouvez tester que le serveur fonctionne correctement en exécutant cette commande dans un terminal:
|
||||
|
||||
```sh
|
||||
curl http://localhost:5000/api
|
||||
# -> doit renvoyer "Bank API v1.0.0" comme résultat
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Repenser la gestion des états
|
||||
|
||||
Dans la [leçon précédente](../../3-data/translations/README.fr.md), nous avons introduit un concept basique d’état dans notre application avec la variable globale `account` qui contient les données bancaires de l’utilisateur actuellement connecté. Cependant, notre mise en œuvre actuelle présente certains défauts. Essayez d’actualiser la page lorsque vous êtes sur le tableau de bord. Que se passe-t-il?
|
||||
|
||||
Il y a 3 problèmes avec le code actuel:
|
||||
|
||||
- L’état n’est pas persistant, car une actualisation du navigateur vous ramène à la page de connexion.
|
||||
- Il existe plusieurs fonctions qui modifient l’état. Au fur et à mesure que l’application se développe, il peut être difficile de suivre les modifications et il est facile d’oublier d’en mettre à jour une.
|
||||
- L’état n’est pas nettoyé, donc lorsque vous cliquez sur *Logout* les données du compte sont toujours là même si vous êtes sur la page de connexion.
|
||||
|
||||
Nous pourrions mettre à jour notre code pour résoudre ces problèmes un par un, mais cela créerait plus de duplication de code et rendrait l’application plus complexe et difficile à maintenir. Ou nous pourrions faire une pause de quelques minutes et repenser notre stratégie.
|
||||
|
||||
> Quels problèmes essayons-nous vraiment de résoudre ici?
|
||||
|
||||
[La gestion de l’état](https://en.wikipedia.org/wiki/State_management) consiste à trouver une bonne approche pour résoudre ces deux problèmes particuliers:
|
||||
|
||||
- Comment rendre compréhensibles les flux de données dans une application?
|
||||
- Comment garder les données d’état toujours synchronisées avec l’interface utilisateur (et vice versa)?
|
||||
|
||||
Une fois que vous vous êtes occupé de ceux-ci, tous les autres problèmes que vous pourriez avoir peuvent être déjà résolus ou sont devenus plus faciles à résoudre. Il existe de nombreuses approches possibles pour résoudre ces problèmes, mais nous opterons pour une solution commune qui consiste à **centraliser les données et les moyens de les modifier**. Les flux de données se dérouleraient comme suit:
|
||||
|
||||

|
||||
|
||||
> Nous ne couvrirons pas ici la partie où les données déclenchent automatiquement la mise à jour de la vue, car elle est liée à des concepts plus avancés de [Programmation réactive](https://en.wikipedia.org/wiki/Reactive_programming). C’est un bon sujet de suivi si vous êtes prêt à plonger profondément.
|
||||
|
||||
✅ Il existe de nombreuses bibliothèques avec différentes approches de la gestion des États, [Redux](https://redux.js.org) étant une option populaire. Jetez un coup d’œil aux concepts et aux modèles utilisés, car c’est souvent un bon moyen d’apprendre quels problèmes potentiels vous pouvez rencontrer dans les grandes applications Web et comment ils peuvent être résolus.
|
||||
|
||||
### Tâche
|
||||
|
||||
Nous allons commencer par un peu de refactorisation. Remplacer la déclaration `account`:
|
||||
|
||||
```js
|
||||
let account = null;
|
||||
```
|
||||
|
||||
Par:
|
||||
|
||||
```js
|
||||
let state = {
|
||||
account: null
|
||||
};
|
||||
```
|
||||
|
||||
L’idée est de *centraliser* toutes nos données d’application dans un seul objet d’état. Nous n’avons que le `account` pour l’instant dans l’état, donc cela ne change pas beaucoup, mais cela crée un chemin pour les évolutions.
|
||||
|
||||
Nous devons également mettre à jour les fonctions en l’utilisant. Dans les fonctions `register()` et `login()`, remplacez `account = ...` par `state.account = ...`;
|
||||
|
||||
En haut de la fonction `updateDashboard()`, ajoutez cette ligne:
|
||||
|
||||
```js
|
||||
const account = state.account;
|
||||
```
|
||||
|
||||
Ce refactoring en lui-même n’a pas apporté beaucoup d’améliorations, mais l’idée était de jeter les bases des prochains changements.
|
||||
|
||||
## Suivre les modifications de données
|
||||
|
||||
Maintenant que nous avons mis en place l’objet `state` pour stocker nos données, l’étape suivante consiste à centraliser les mises à jour. L’objectif est de faciliter le suivi des changements et de leur moment.
|
||||
|
||||
Pour éviter que des modifications soient apportées à l’objet `state`, il est également recommandé de le considérer comme [*immuable*](https://en.wikipedia.org/wiki/Immutable_object), ce qui signifie qu’il ne peut pas être modifié du tout. Cela signifie également que vous devez créer un nouvel objet d’état si vous souhaitez y modifier quoi que ce soit. Ce faisant, vous créez une protection contre les [effets secondaires](https://en.wikipedia.org/wiki/Side_effect_(computer_science)) potentiellement indésirables et ouvrez des possibilités de nouvelles fonctionnalités dans votre application, telles que la mise en œuvre de l’annulation/rétablissement, tout en facilitant le débogage. Par exemple, vous pouvez consigner chaque modification apportée à l’état et conserver un historique des modifications pour comprendre la source d’un bogue.
|
||||
|
||||
En JavaScript, vous pouvez utiliser [`Object.freeze()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) pour créer une version immuable d’un objet. Si vous essayez d’apporter des modifications à un objet immuable, une exception sera déclenchée.
|
||||
|
||||
✅ Connaissez-vous la différence entre un objet immuable *peu profond* et un objet immuable *profond*? Vous pouvez en apprendre plus sur ce sujet [ici](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze#What_is_shallow_freeze).
|
||||
|
||||
### Tâche
|
||||
|
||||
Créons une nouvelle fonction `updateState()`:
|
||||
|
||||
```js
|
||||
function updateState(property, newData) {
|
||||
state = Object.freeze({
|
||||
...state,
|
||||
[property]: newData
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
Dans cette fonction, nous créons un nouvel objet d’état et copions les données de l’état précédent à l’aide de [*l’opérateur spread (`...`)*](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/Spread_syntax#Spread_in_object_literals). Ensuite, nous remplaçons une propriété particulière de l’objet d’état par les nouvelles données en utilisant la [notation entre crochets](https://developer.mozilla.org/docs/Web/JavaScript/Guide/Working_with_Objects#Objects_and_properties) `[property]` pour l’affectation. Enfin, nous verrouillons l’objet pour empêcher les modifications en utilisant `Object.freeze()`. Nous n’avons que la propriété `account` stockée dans l’état pour l’instant, mais avec cette approche, vous pouvez ajouter autant de propriétés que nécessaire dans l’état.
|
||||
|
||||
Nous allons également mettre à jour l’initialisation `state` pour nous assurer que l’état initial est également gelé:
|
||||
|
||||
```js
|
||||
let state = Object.freeze({
|
||||
account: null
|
||||
});
|
||||
```
|
||||
|
||||
Après cela, mettez à jour la fonction `register` en remplaçant l’affectation `state.account = result;` par:
|
||||
|
||||
```js
|
||||
updateState('account', result);
|
||||
```
|
||||
|
||||
Faites de même avec la fonction `login`, en remplaçant `state.account = data;` par:
|
||||
|
||||
```js
|
||||
updateState('account', data);
|
||||
```
|
||||
|
||||
Nous allons maintenant saisir l’occasion de résoudre le problème des données de compte qui ne sont pas effacées lorsque l’utilisateur clique sur *Logout*.
|
||||
|
||||
Créez une nouvelle fonction `logout()`:
|
||||
|
||||
```js
|
||||
function logout() {
|
||||
updateState('account', null);
|
||||
navigate('/login');
|
||||
}
|
||||
```
|
||||
|
||||
Dans `updateDashboard()`, remplacez la redirection `return navigate('/login');` par `return logout()`;
|
||||
|
||||
Essayez d’enregistrer un nouveau compte, de vous déconnecter et de vous reconnecter pour vérifier que tout fonctionne toujours correctement.
|
||||
|
||||
> Conseil: vous pouvez jeter un coup d’œil à tous les changements d’état en ajoutant `console.log(state)` au bas de `updateState()` et en ouvrant la console dans les outils de développement de votre navigateur.
|
||||
|
||||
## Conserver l’état
|
||||
|
||||
La plupart des applications Web doivent conserver les données pour pouvoir fonctionner correctement. Toutes les données critiques sont généralement stockées dans une base de données et accessibles via une API de serveur, comme les données du compte utilisateur dans notre cas. Mais parfois, il est également intéressant de conserver certaines données sur l’application cliente qui s’exécute dans votre navigateur, pour une meilleure expérience utilisateur ou pour améliorer les performances de chargement.
|
||||
|
||||
Lorsque vous souhaitez conserver des données dans votre navigateur, vous devez vous poser quelques questions importantes:
|
||||
|
||||
- *Les données sont-elles sensibles?* Vous devez éviter de stocker des données sensibles sur le client, telles que les mots de passe des utilisateurs.
|
||||
- *Pendant combien de temps devez-vous conserver ces données?* Prévoyez-vous d’accéder à ces données uniquement pour la session en cours ou souhaitez-vous qu’elles soient stockées pour toujours?
|
||||
|
||||
Il existe plusieurs façons de stocker des informations dans une application web, en fonction de ce que vous souhaitez réaliser. Par exemple, vous pouvez utiliser les URL pour stocker une requête de recherche et la rendre partageable entre les utilisateurs. Vous pouvez également utiliser des [cookies HTTP](https://developer.mozilla.org/docs/Web/HTTP/Cookies) si les données doivent être partagées avec le serveur, comme les informations d'[authentification](https://en.wikipedia.org/wiki/Authentication).
|
||||
|
||||
Une autre option consiste à utiliser l’une des nombreuses API de navigateur pour stocker des données. Deux d’entre eux sont particulièrement intéressants:
|
||||
|
||||
- [`localStorage`](https://developer.mozilla.org/docs/Web/API/Window/localStorage): un [Key/Value store](https://en.wikipedia.org/wiki/Key%E2%80%93value_database) permettant de conserver des données spécifiques au site web actuel sur différentes sessions. Les données qui y sont enregistrées n’expirent jamais.
|
||||
- [`sessionStorage`](https://developer.mozilla.org/docs/Web/API/Window/sessionStorage): celui-ci fonctionne de la même manière que `localStorage` sauf que les données qui y sont stockées sont effacées à la fin de la session (lorsque le navigateur est fermé).
|
||||
|
||||
Notez que ces deux API autorisent uniquement le stockage de [chaînes](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String). Si vous souhaitez stocker des objets complexes, vous devrez les sérialiser au format [JSON](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/JSON) à l’aide de [`JSON.stringify()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify).
|
||||
|
||||
✅ Si vous souhaitez créer une application web qui ne fonctionne pas avec un serveur, il est également possible de créer une base de données sur le client à l’aide de [l’API `IndexedDB`](https://developer.mozilla.org/docs/Web/API/IndexedDB_API). Celui-ci est réservé aux cas d’utilisation avancés ou si vous avez besoin de stocker une quantité importante de données, car il est plus complexe à utiliser.
|
||||
|
||||
### Tâche
|
||||
|
||||
Nous voulons que nos utilisateurs restent connectés jusqu’à ce qu’ils cliquent explicitement sur le bouton *Logout*, nous utiliserons donc `localStorage` pour stocker les données du compte. Tout d’abord, définissons une clé que nous utiliserons pour stocker nos données.
|
||||
|
||||
```js
|
||||
const storageKey = 'savedAccount';
|
||||
```
|
||||
|
||||
Ajoutez ensuite cette ligne à la fin de la fonction `updateState()`:
|
||||
|
||||
```js
|
||||
localStorage.setItem(storageKey, JSON.stringify(state.account));
|
||||
```
|
||||
|
||||
Avec cela, les données du compte utilisateur seront conservées et toujours à jour car nous avons centralisé précédemment toutes nos mises à jour d’état. C’est là que nous commençons à bénéficier de tous nos refactors précédents 🙂.
|
||||
|
||||
Au fur et à mesure que les données sont enregistrées, nous devons également nous occuper de les restaurer lorsque l’application est chargée. Puisque nous allons commencer à avoir plus de code d’initialisation, il peut être judicieux de créer une nouvelle fonction `init`, qui inclut également notre code précédent au bas de `app.js`:
|
||||
|
||||
```js
|
||||
function init() {
|
||||
const savedAccount = localStorage.getItem(storageKey);
|
||||
if (savedAccount) {
|
||||
updateState('account', JSON.parse(savedAccount));
|
||||
}
|
||||
|
||||
// Notre précédent code d'initialisation
|
||||
window.onpopstate = () => updateRoute();
|
||||
updateRoute();
|
||||
}
|
||||
|
||||
init();
|
||||
```
|
||||
|
||||
Ici, nous récupérons les données enregistrées, et s’il y en a, nous mettons à jour l’état en conséquence. Il est important de le faire *avant* de mettre à jour l’itinéraire, car il peut y avoir du code s’appuyant sur l’état lors de la mise à jour de la page.
|
||||
|
||||
Nous pouvons également faire de la page *Dashboard* notre page par défaut de l’application, car nous conservons maintenant les données du compte. Si aucune donnée n’est trouvée, le tableau de bord se charge de rediriger vers la page *Login* de toute façon. Dans `updateRoute()`, remplacez le secours `return navigate('/login');` par `return navigate('/dashboard');`.
|
||||
|
||||
Maintenant, connectez-vous à l’application et essayez d’actualiser la page. Vous devez rester sur le tableau de bord. Avec cette mise à jour, nous avons résolu tous nos problèmes initiaux...
|
||||
|
||||
## Actualiser les données
|
||||
|
||||
... Mais nous pourrions aussi en avoir créé un nouveau. Oups!
|
||||
|
||||
Accédez au tableau de bord à l’aide du compte `test`, puis exécutez cette commande sur un terminal pour créer une nouvelle transaction:
|
||||
|
||||
```sh
|
||||
curl --request POST \
|
||||
--header "Content-Type: application/json" \
|
||||
--data "{ \"date\": \"2020-07-24\", \"object\": \"Bought book\", \"amount\": -20 }" \
|
||||
http://localhost:5000/api/accounts/test/transactions
|
||||
```
|
||||
|
||||
Essayez d’actualiser la page du tableau de bord dans le navigateur maintenant. Que se passe-t-il? Voyez-vous la nouvelle transaction?
|
||||
|
||||
L’état est conservé indéfiniment grâce au `localStorage`, mais cela signifie également qu’il n’est jamais mis à jour tant que vous ne vous déconnectez pas de l’application et que vous ne vous connectez pas à nouveau!
|
||||
|
||||
Une stratégie possible pour résoudre ce problème consiste à recharger les données du compte chaque fois que le tableau de bord est chargé, afin d’éviter les données de blocage.
|
||||
|
||||
### Tâche
|
||||
|
||||
Créez une nouvelle fonction `updateAccountData`:
|
||||
|
||||
```js
|
||||
async function updateAccountData() {
|
||||
const account = state.account;
|
||||
if (!account) {
|
||||
return logout();
|
||||
}
|
||||
|
||||
const data = await getAccount(account.user);
|
||||
if (data.error) {
|
||||
return logout();
|
||||
}
|
||||
|
||||
updateState('account', data);
|
||||
}
|
||||
```
|
||||
|
||||
Cette méthode vérifie que nous sommes actuellement connectés puis recharge les données du compte à partir du serveur.
|
||||
|
||||
Créez une autre fonction nommée `refresh`:
|
||||
|
||||
```js
|
||||
async function refresh() {
|
||||
await updateAccountData();
|
||||
updateDashboard();
|
||||
}
|
||||
```
|
||||
|
||||
Celui-ci met à jour les données du compte, puis se charge de mettre à jour le code HTML de la page du tableau de bord. C’est ce que nous devons appeler lorsque l’itinéraire du tableau de bord est chargé. Mettez à jour la définition d’itinéraire avec:
|
||||
|
||||
```js
|
||||
const routes = {
|
||||
'/login': { templateId: 'login' },
|
||||
'/dashboard': { templateId: 'dashboard', init: refresh }
|
||||
};
|
||||
```
|
||||
|
||||
Essayez de recharger le tableau de bord maintenant, il devrait afficher les données de compte mises à jour.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Challenge
|
||||
|
||||
Maintenant que nous rechargeons les données du compte chaque fois que le tableau de bord est chargé, pensez-vous que nous devons encore conserver *toutes les données du compte*?
|
||||
|
||||
Essayez de travailler ensemble pour modifier ce qui est enregistré et chargé à partir de `localStorage` pour n’inclure que ce qui est absolument nécessaire pour que l’application fonctionne.
|
||||
|
||||
## Quiz de validation des connaissances
|
||||
|
||||
[Quiz de validation des connaissances](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/48?loc=fr)
|
||||
|
||||
## Affectation
|
||||
|
||||
[Implémenter la boîte de dialogue "Ajouter une transaction"](assignment.fr.md)
|
||||
|
||||
Voici un exemple de résultat après avoir terminé l’affectation:
|
||||
|
||||

|
@@ -1,284 +0,0 @@
|
||||
# एक बैंकिंग ऐप का निर्माण करें भाग 4: स्टेट प्रबंधन की अवधारणा
|
||||
|
||||
## पूर्व व्याख्यान प्रश्नोत्तरी
|
||||
|
||||
[पूर्व व्याख्यान प्रश्नोत्तरी](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/47?loc=hi)
|
||||
|
||||
### परिचय
|
||||
|
||||
जैसे-जैसे वेब एप्लिकेशन बढ़ता है, यह सभी डेटा फ्लो पर नज़र रखना एक चुनौती बन जाता है। किस कोड को डेटा मिलता है, कौन सा पेज इसका उपभोग करता है, कहां और कब इसे अपडेट करने की आवश्यकता होती है ... गंदे कोड को समाप्त करना आसान है जिसे बनाए रखना मुश्किल है। यह विशेष रूप से सच है जब आपको अपने ऐप के विभिन्न पृष्ठों के बीच डेटा साझा करने की आवश्यकता होती है, उदाहरण के लिए उपयोगकर्ता डेटा। *स्टेट प्रबंधन* की अवधारणा हमेशा सभी प्रकार के कार्यक्रमों में मौजूद रही है, लेकिन जैसा कि वेब ऐप जटिलता में बढ़ रहे हैं, यह अब विकास के दौरान सोचने का एक महत्वपूर्ण बिंदु है।
|
||||
|
||||
इस अंतिम भाग में, हम उस ऐप पर नज़र डालेंगे जिसे हमने बनाया है कि स्टेट कैसे प्रबंधित किया जाता है, किसी भी बिंदु पर ब्राउज़र रीफ्रेश के लिए समर्थन और उपयोगकर्ता सत्रों में डेटा को बनाए रखने की अनुमति देता है।
|
||||
|
||||
### शर्त
|
||||
|
||||
आपको इस पाठ के लिए वेब ऐप का [डेटा प्राप्त करने](../../3-data/translations/README.hi.md) वाला भाग पूरा करना होगा। आपको स्थानीय रूप से [Node.js](https://nodejs.org) और [सर्वर एपीआई चलाने](../../api/translations/README.hi.md) को स्थापित करने की आवश्यकता है ताकि आप खाता डेटा प्रबंधित कर सकें।
|
||||
|
||||
आप परीक्षण कर सकते हैं कि सर्वर टर्मिनल में इस कमांड को निष्पादित करके ठीक से चल रहा है:
|
||||
|
||||
```sh
|
||||
curl http://localhost:5000/api
|
||||
# -> should return "Bank API v1.0.0" as a result
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## स्टेट प्रबंधन पुनर्विचार
|
||||
|
||||
[पिछले पाठ](../../3-data/translations/README.hi.md) में, हमने अपने ऐप में वैश्विक `account` चर के साथ स्टेट की एक बुनियादी अवधारणा पेश की, जिसमें वर्तमान में लॉग इन उपयोगकर्ता के लिए बैंक डेटा शामिल है। हालांकि, हमारे वर्तमान कार्यान्वयन में कुछ खामियां हैं। जब आप डैशबोर्ड पर हों तो पृष्ठ को ताज़ा करने का प्रयास करें। क्या होता है?
|
||||
|
||||
वर्तमान कोड के साथ 3 समस्याएँ हैं:
|
||||
|
||||
- स्टेट कायम नहीं है, क्योंकि ब्राउज़र रीफ़्रेश आपको लॉगिन पृष्ठ पर वापस ले जाता है।
|
||||
- स्टेट को संशोधित करने वाले कई कार्य हैं। जैसे-जैसे ऐप बढ़ता है, यह परिवर्तनों को ट्रैक करना मुश्किल बना सकता है और किसी एक को अपडेट करना भूल जाना आसान है।
|
||||
- स्टेट को साफ नहीं किया जाता है, इसलिए जब आप * लॉगआउट * पर क्लिक करते हैं, तो खाता डेटा अभी भी वहीं है जबकि आप लॉगिन पेज पर हैं।
|
||||
|
||||
हम एक-एक करके इन मुद्दों से निपटने के लिए अपने कोड को अपडेट कर सकते हैं, लेकिन यह अधिक कोड दोहराव पैदा करेगा और ऐप को अधिक जटिल और बनाए रखना मुश्किल होगा। या हम कुछ मिनटों के लिए रुक सकते हैं और अपनी रणनीति पर फिर से विचार कर सकते हैं।
|
||||
|
||||
>
|
||||
हम वास्तव में किन समस्याओं को हल करने की कोशिश कर रहे हैं?
|
||||
|
||||
[स्टेट प्रबंधन](https://en.wikipedia.org/wiki/State_management) इन दो विशेष समस्याओं को हल करने के लिए एक अच्छा तरीका खोजने के बारे में है:
|
||||
|
||||
- ऐप में डेटा फ्लो को कैसे समझा जा सकता है?
|
||||
- उपयोगकर्ता इंटरफ़ेस (और इसके विपरीत) के साथ स्टेट के डेटा को हमेशा सिंक में कैसे रखा जाए?
|
||||
|
||||
एक बार जब आप इनका ध्यान रख लेते हैं, तो हो सकता है कि कोई अन्य समस्या या तो पहले से ही ठीक हो जाए या जिसे ठीक करना आसान हो जाए। इन समस्याओं को हल करने के लिए कई संभावित दृष्टिकोण हैं, लेकिन हम एक सामान्य समाधान के साथ जाएंगे जिसमें डेटा को **केंद्रीकृत करना और इसे बदलने के तरीके** शामिल हैं। डेटा प्रवाह इस तरह होगा:
|
||||
|
||||

|
||||
|
||||
> हम यहां उस हिस्से को कवर नहीं करेंगे जहां डेटा स्वचालित रूप से दृश्य अद्यतन को ट्रिगर करता है, क्योंकि यह [रीऐक्टिव प्रोग्रामिंग](https://en.wikipedia.org/wiki/Reactive_programming) की अधिक उन्नत अवधारणाओं से बंधा है। यदि आप एक गहरी गोता लगाने के लिए एक अच्छा अनुवर्ती विषय है।
|
||||
|
||||
✅ स्टेट प्रबंधन के विभिन्न दृष्टिकोणों के साथ वहाँ बहुत सारे पुस्तकालय हैं, [Redux](https://redux.js.org) एक लोकप्रिय विकल्प है। उपयोग की जाने वाली अवधारणाओं और पैटर्नों पर एक नज़र डालें क्योंकि यह अक्सर सीखने का एक अच्छा तरीका है कि आप बड़े वेब ऐप में किन संभावित मुद्दों का सामना कर रहे हैं और इसे कैसे हल किया जा सकता है।
|
||||
|
||||
### टास्क
|
||||
|
||||
हम थोड़ा सा रिफैक्टरिंग के साथ शुरुआत करेंगे। `account` घोषणा बदलें:
|
||||
|
||||
```js
|
||||
let account = null;
|
||||
```
|
||||
|
||||
With:
|
||||
|
||||
```js
|
||||
let state = {
|
||||
account: null
|
||||
};
|
||||
```
|
||||
|
||||
एक स्टेट वस्तु में हमारे सभी एप्लिकेशन डेटा को *केंद्रीकृत* करने का विचार है। हमारे पास स्टेट में अभी के लिए `account` है, इसलिए यह बहुत अधिक नहीं बदलता है, लेकिन यह प्रस्तावों के लिए एक रास्ता बनाता है।
|
||||
|
||||
|
||||
हमें इसका उपयोग करके कार्यों को भी अपडेट करना होगा। `register()` और `login()` फंगक्शनसमे,`account = ...` को `state.account = ...` से बदले;
|
||||
|
||||
`UpdateDashboard()` फ़ंक्शन के शीर्ष पर, यह पंक्ति जोड़ें:
|
||||
|
||||
```js
|
||||
const account = state.account;
|
||||
```
|
||||
|
||||
अपने आप में इस रिफ्रैक्टिंग में बहुत सुधार नहीं हुआ, लेकिन विचार अगले बदलावों की नींव रखने का था।
|
||||
|
||||
## डेटा परिवर्तन ट्रैक करें
|
||||
|
||||
अब जब हमने अपने डेटा को स्टोर करने के लिए `state` ऑब्जेक्ट को रखा है, तो अगला चरण अपडेट को केंद्रीकृत करना है। लक्ष्य किसी भी परिवर्तन का ट्रैक रखना आसान है और जब वे होते हैं।
|
||||
|
||||
`state` वस्तु में किए गए परिवर्तनों से बचने के लिए, यह [*अपरिवर्तनीय*](https://en.wikipedia.org/wiki/Immutable_object) पर विचार करने के लिए एक अच्छा अभ्यास है, जिसका अर्थ है कि इसे बिल्कुल भी संशोधित नहीं किया जा सकता है। इसका अर्थ यह भी है कि यदि आप इसमें कुछ भी बदलना चाहते हैं तो आपको एक नया स्टेट ऑब्जेक्ट बनाना होगा। ऐसा करने से, आप संभावित रूप से अवांछित [साइड इफेक्ट्स](https://en.wikipedia.org/wiki/Side_effect_(computer_science)) के बारे में एक सुरक्षा का निर्माण करते हैं, और अपने ऐप में नई सुविधाओं के लिए संभावनाएं खोलते हैं जैसे कि undo/redo को लागू करना, जबकि डिबग करना भी आसान है। उदाहरण के लिए, आप स्टेट में किए गए प्रत्येक परिवर्तन को लॉग कर सकते हैं और बग के स्रोत को समझने के लिए परिवर्तनों का इतिहास रख सकते हैं।
|
||||
|
||||
जावास्क्रिप्ट में, आप एक अपरिवर्तनीय संस्करण एक ऑब्जेक्ट बनाने के लिए [`Object.freeze()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) का उपयोग कर सकते हैं । यदि आप एक अपरिवर्तनीय वस्तु में परिवर्तन करने की कोशिश करते हैं, तो एक अपवाद उठाया जाएगा।
|
||||
|
||||
✅ क्या आप एक *उथले* और एक *गहरी* अपरिवर्तनीय वस्तु के बीच का अंतर जानते हैं? आप इसके बारे में [यहां](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze#What_is_shallow_freeze) पढ़ सकते हैं.
|
||||
|
||||
### टास्क
|
||||
|
||||
Let's create a new `updateState()` function:
|
||||
|
||||
```js
|
||||
function updateState(property, newData) {
|
||||
state = Object.freeze({
|
||||
...state,
|
||||
[property]: newData
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
इस फ़ंक्शन में, हम [*spread (`...`) operator*](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/Spread_syntax#Spread_in_object_literals) का उपयोग करके पिछले स्टेट से एक नया स्टेट ऑब्जेक्ट और कॉपी डेटा बना रहे हैं। फिर हम नए डेटा के साथ स्टेट ऑब्जेक्ट की एक विशेष प्रॉपर्टी को ओवरराइड करते हैं [ब्रैकेट नोटेशन] `[property]` असाइनमेंट के लिए। अंत में, हम `Object.freeze()` का उपयोग करके संशोधनों को रोकने के लिए ऑब्जेक्ट को लॉक करते हैं। हमारे पास अब केवल स्टेट में संग्रहीत `अकाउंट` प्रॉपर्टी है, लेकिन इस दृष्टिकोण के साथ आप स्टेट में जितनी आवश्यकता हो उतने गुण जोड़ सकते हैं।
|
||||
|
||||
हम यह भी सुनिश्चित करेंगे कि प्रारंभिक अवस्था भी जम गई है, यह सुनिश्चित करने के लिए `state` आरंभीकरण को अद्यतन करेगा:
|
||||
|
||||
```js
|
||||
let state = Object.freeze({
|
||||
account: null
|
||||
});
|
||||
```
|
||||
|
||||
उसके बाद, `state.account = result` के स्थान पर `register` फ़ंक्शन को अपडेट करें; असाइनमेंट के साथ:
|
||||
|
||||
```js
|
||||
updateState('account', result);
|
||||
```
|
||||
|
||||
`login` फ़ंक्शन के साथ भी ऐसा ही करें, `state.account = data;` के स्थान पर:
|
||||
|
||||
```js
|
||||
updateState('account', data);
|
||||
```
|
||||
|
||||
जब उपयोगकर्ता *लॉगआउट* पर क्लिक करेगा तो हम खाता डेटा के मुद्दे को ठीक करने का मौका नहीं लेंगे।
|
||||
|
||||
एक नया `logout()` फंगक्शन बनाए:
|
||||
|
||||
```js
|
||||
function logout() {
|
||||
updateState('account', null);
|
||||
navigate('/login');
|
||||
}
|
||||
```
|
||||
|
||||
`updateDashboard()` मे , पुनर्निर्देशन `return navigate('/login');` की जगह `return logout()` के साथ;
|
||||
|
||||
एक नया खाता पंजीकृत करने की कोशिश करें, लॉग आउट करें और फिर से जाँच करें कि सब कुछ अभी भी सही ढंग से काम करता है।
|
||||
|
||||
युक्ति: आप `updateState()` के तल पर `console.log(state)` जोड़कर और अपने ब्राउज़र के डेवलपमेंट टूल में कंसोल खोलकर सभी स्टेट परिवर्तनों पर एक नज़र डाल सकते हैं।
|
||||
|
||||
## स्टेट को पर्सिस्ट करे
|
||||
|
||||
अधिकांश वेब ऐप्स को डेटा को सही ढंग से काम करने में सक्षम बनाने के लिए लगातार बने रहने की आवश्यकता होती है। सभी महत्वपूर्ण डेटा को आमतौर पर डेटाबेस पर संग्रहीत किया जाता है और सर्वर एपीआई के माध्यम से एक्सेस किया जाता है, जैसे हमारे मामले में उपयोगकर्ता खाता डेटा। लेकिन कभी-कभी, एक बेहतर उपयोगकर्ता अनुभव के लिए या लोडिंग प्रदर्शन में सुधार करने के लिए आपके ब्राउज़र में चल रहे क्लाइंट ऐप पर कुछ डेटा को जारी रखना भी दिलचस्प है।
|
||||
|
||||
जब आप अपने ब्राउज़र में डेटा को पर्सिस्ट रखना चाहते हैं, तो कुछ महत्वपूर्ण सवाल हैं जो आपको खुद से पूछना चाहिए:
|
||||
|
||||
- *क्या डेटा संवेदनशील है?* आपको क्लाइंट पर किसी भी संवेदनशील डेटा, जैसे उपयोगकर्ता पासवर्ड को संग्रहीत करने से बचना चाहिए।
|
||||
- *आपको यह डेटा कब तक रखने की आवश्यकता है?* क्या आप इस डेटा को केवल वर्तमान सत्र के लिए एक्सेस करने की योजना बना रहे हैं या क्या आप चाहते हैं कि यह हमेशा के लिए स्टोर हो जाए?
|
||||
|
||||
वेब ऐप के अंदर जानकारी संग्रहीत करने के कई तरीके हैं, जो इस बात पर निर्भर करता है कि आप क्या हासिल करना चाहते हैं। उदाहरण के लिए, आप खोज क्वेरी को संग्रहीत करने के लिए URL का उपयोग कर सकते हैं, और इसे उपयोगकर्ताओं के बीच साझा करने योग्य बना सकते हैं। यदि आप डेटा को सर्वर के साथ साझा करने की आवश्यकता है, जैसे कि [authentication](https://en.wikipedia.org/wiki/Authentication) की जानकारी।
|
||||
|
||||
|
||||
एक अन्य विकल्प डेटा भंडारण के लिए कई ब्राउज़र एपीआई में से एक का उपयोग करना है। उनमें से दो विशेष रूप से दिलचस्प हैं:
|
||||
|
||||
- [`localStorage`](https://developer.mozilla.org/docs/Web/API/Window/localStorage): एक [Key/Value store](https://en.wikipedia.org/wiki/Key%E2%80%93value_database) विभिन्न सत्रों में वर्तमान वेब साइट के लिए विशिष्ट डेटा को बनाए रखने की अनुमति देते हैं। इसमें सहेजा गया डेटा कभी समाप्त नहीं होता है।
|
||||
- [`sessionStorage`](https://developer.mozilla.org/docs/Web/API/Window/sessionStorage): यह एक `sessionStorage` की तरह ही काम करता है, सिवाय इसके कि इसमें संग्रहीत डेटा सत्र समाप्त होने पर (जब ब्राउज़र बंद हो जाता है) साफ हो जाता है।
|
||||
|
||||
यदि आप जटिल वस्तुओं को संग्रहीत करना चाहते हैं, तो आपको इसे [JSON](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/JSON) प्रारूप [`JSON.stringify()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) का उपयोग करके क्रमबद्ध करना होगा।
|
||||
|
||||
✅ यदि आप एक वेब ऐप बनाना चाहते हैं जो सर्वर के साथ काम नहीं करता है, तो [`IndexedDB` API](https://developer.mozilla.org/docs/Web/API/IndexedDB_API) का उपयोग करके क्लाइंट पर डेटाबेस बनाना भी संभव है। यह एक उन्नत उपयोग मामलों के लिए आरक्षित है या यदि आपको महत्वपूर्ण मात्रा में डेटा संग्रहीत करने की आवश्यकता है, क्योंकि यह उपयोग करने के लिए अधिक जटिल है।
|
||||
|
||||
### टास्क
|
||||
|
||||
हम चाहते हैं कि हमारे उपयोगकर्ता तब तक लॉग इन रहें जब तक कि वे स्पष्ट रूप से *लॉगआउट* बटन पर क्लिक न करें, इसलिए हम खाता डेटा संग्रहीत करने के लिए `localStorage` का उपयोग करेंगे। सबसे पहले, एक कुंजी परिभाषित करते हैं जिसका उपयोग हम अपने डेटा को संग्रहीत करने के लिए करेंगे।.
|
||||
|
||||
```js
|
||||
const storageKey = 'savedAccount';
|
||||
```
|
||||
|
||||
फिर इस लाइन को `updateState()` फ़ंक्शन के अंत में जोड़ें:
|
||||
|
||||
```js
|
||||
localStorage.setItem(storageKey, JSON.stringify(state.account));
|
||||
```
|
||||
|
||||
इसके साथ, उपयोगकर्ता खाते के डेटा को बनाए रखा जाएगा और हमेशा अप-टू-डेट रहेगा क्योंकि हमने अपने सभी स्टेट अपडेट पहले केंद्रीकृत किए थे। यह वह जगह है जहाँ हम अपने सभी पिछले रिफ्लेक्टरों से लाभान्वित होने लगते हैं 🙂.
|
||||
|
||||
डेटा सहेजे जाने के साथ, हमें ऐप को लोड करने पर इसे पुनर्स्थापित करने का भी ध्यान रखना होगा। चूंकि हम अधिक आरंभीकरण कोड शुरू करेंगे, इसलिए यह एक नया `init` फ़ंक्शन बनाने के लिए एक अच्छा विचार हो सकता है, जिसमें `app.js` के नीचे हमारा पिछला कोड भी शामिल है।:
|
||||
|
||||
```js
|
||||
function init() {
|
||||
const savedAccount = localStorage.getItem(storageKey);
|
||||
if (savedAccount) {
|
||||
updateState('account', JSON.parse(savedAccount));
|
||||
}
|
||||
|
||||
// Our previous initialization code
|
||||
window.onpopstate = () => updateRoute();
|
||||
updateRoute();
|
||||
}
|
||||
|
||||
init();
|
||||
```
|
||||
|
||||
यहां हम सहेजे गए डेटा को पुनर्प्राप्त करते हैं, और यदि कोई है तो हम तदनुसार स्टेट को अपडेट करते हैं। रूट अपडेट करने से *पहले* इसे करना महत्वपूर्ण है, क्योंकि पेज अपडेट के दौरान स्टेट पर कोड निर्भर हो सकता है।
|
||||
|
||||
हम अपने एप्लिकेशन डिफॉल्ट पेज को भी * डैशबोर्ड * पेज बना सकते हैं, क्योंकि हम अब खाता डेटा को जारी रख रहे हैं। यदि कोई डेटा नहीं मिला है, तो डैशबोर्ड * लॉगिन * पेज वैसे भी पुनर्निर्देशित करने का ख्याल रखता है। `updateRoute()` में, फ़ॉलबैक `return navigate('/login');` को `return navigate('/dashboard');` से बदलें।
|
||||
|
||||
अब ऐप में लॉगइन करें और पेज को रिफ्रेश करने की कोशिश करें। आपको डैशबोर्ड पर रहना चाहिए। उस अपडेट के साथ हमने अपने सभी शुरुआती मुद्दों का ध्यान रखा है ...
|
||||
|
||||
## डाटाको रिफ्रेश करे
|
||||
|
||||
...लेकिन हम एक नया भी बना सकते हैं। ऊपस!
|
||||
|
||||
`test` खाते का उपयोग करके डैशबोर्ड पर जाएं, फिर एक नया लेनदेन बनाने के लिए इस कमांड को टर्मिनल पर चलाएं:
|
||||
|
||||
```sh
|
||||
curl --request POST \
|
||||
--header "Content-Type: application/json" \
|
||||
--data "{ \"date\": \"2020-07-24\", \"object\": \"Bought book\", \"amount\": -20 }" \
|
||||
http://localhost:5000/api/accounts/test/transactions
|
||||
```
|
||||
|
||||
अब ब्राउज़र में अपने डैशबोर्ड पृष्ठ को रिफ्रेश करने का प्रयास करें। क्या होता है? क्या आप नया लेनदेन देखते हैं?
|
||||
|
||||
स्टेट अनिश्चित काल तक `localStorage` की बदौलत कायम है, लेकिन इसका मतलब यह भी है कि यह तब तक अपडेट नहीं किया जाता जब तक आप ऐप से लॉग-आउट नहीं करते और फिर से लॉग इन नहीं करते!
|
||||
|
||||
स्टेक डेटा से बचने के लिए, हर बार डैशबोर्ड को लोड करने के बाद खाते के डेटा को फिर से लोड करना एक निश्चित रणनीति है।
|
||||
|
||||
### टास्क
|
||||
|
||||
एक नया फंगक्शन `updateAccountData` बनाए:
|
||||
|
||||
```js
|
||||
async function updateAccountData() {
|
||||
const account = state.account;
|
||||
if (!account) {
|
||||
return logout();
|
||||
}
|
||||
|
||||
const data = await getAccount(account.user);
|
||||
if (data.error) {
|
||||
return logout();
|
||||
}
|
||||
|
||||
updateState('account', data);
|
||||
}
|
||||
```
|
||||
|
||||
यह विधि जांचती है कि हम वर्तमान में लॉग इन हैं फिर सर्वर से खाता डेटा पुनः लोड करता है।
|
||||
|
||||
एक दूसरा नया `refresh` नामका फंगक्शन बनाए:
|
||||
|
||||
```js
|
||||
async function refresh() {
|
||||
await updateAccountData();
|
||||
updateDashboard();
|
||||
}
|
||||
```
|
||||
|
||||
यह एक खाता डेटा अपडेट करता है, फिर डैशबोर्ड पृष्ठ के HTML को अपडेट करने का ध्यान रखता है। डैशबोर्ड मार्ग लोड होने पर हमें यह कॉल करना होगा। इसके साथ रूट की परिभाषा को अपडेट करें:
|
||||
|
||||
```js
|
||||
const routes = {
|
||||
'/login': { templateId: 'login' },
|
||||
'/dashboard': { templateId: 'dashboard', init: refresh }
|
||||
};
|
||||
```
|
||||
|
||||
डैशबोर्ड को अब पुनः लोड करने का प्रयास करें, इसे अद्यतन खाता डेटा प्रदर्शित करना चाहिए।
|
||||
|
||||
---
|
||||
|
||||
## 🚀 चुनौती
|
||||
|
||||
अब जब हम डैशबोर्ड लोड होने के बाद हर बार खाते के डेटा को लोड करते हैं, तो क्या आपको लगता है कि हमें अभी भी *सभी खाते* डेटा को बनाए रखने की आवश्यकता है?
|
||||
|
||||
सहेजने और लोड करने के लिए एक साथ काम करने का प्रयास करें `localStorage` से केवल उस ऐप में काम करने के लिए जो आवश्यक है उसे शामिल करें।
|
||||
|
||||
## व्याख्यान उपरांत प्रश्नोत्तरी
|
||||
|
||||
[व्याख्यान उपरांत प्रश्नोत्तरी](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/48?loc=hi)
|
||||
|
||||
## असाइनमेंट
|
||||
|
||||
["ट्रैन्सैक्शन जोड़े" डायलॉग इम्प्लमेन्ट करे](assignment.hi.md)
|
||||
|
||||
यहां असाइनमेंट पूरा करने के बाद एक उदाहरण दिया गया है:
|
||||
|
||||

|
@@ -1,281 +0,0 @@
|
||||
# Creare un'App Bancaria Parte 4: Concetti di Gestione dello Stato
|
||||
|
||||
## Quiz Pre-Lezione
|
||||
|
||||
[Quiz Pre-Lezione](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/47?loc=it)
|
||||
|
||||
### Introduzione
|
||||
|
||||
Man mano che un'applicazione web cresce, diventa una sfida tenere traccia di tutti i flussi di dati. Quale codice riceve i dati, quale pagina li consuma, dove e quando deve essere aggiornata ... è facile ritrovarsi con codice disordinato e difficile da mantenere. Ciò è particolarmente vero quando è necessario condividere dati tra diverse pagine della propria app, ad esempio i dati dell'utente. Il concetto di *gestione dello stato* è sempre esistito in tutti i tipi di programmi, ma poiché le app web continuano a crescere in complessità, ora è un punto chiave su cui riflettere durante lo sviluppo.
|
||||
|
||||
In questa parte finale, si esaminerà l'app creata per ripensare a come viene gestito lo stato, consentendo il supporto per l'aggiornamento del browser in qualsiasi momento e persistendo i dati tra le sessioni utente.
|
||||
|
||||
### Prerequisito
|
||||
|
||||
È necessario aver completato la parte di [recupero dei dati](../../3-data/translations/README.it.md) dell'app web per questa lezione. È inoltre necessario installare [Node.js](https://nodejs.org) ed [eseguire l'API del server](../../api/translations/README.it.md) in locale in modo da ottenere i dati dell'account.
|
||||
|
||||
Si può verificare che il server funzioni correttamente eseguendo questo comando in un terminale:
|
||||
|
||||
```sh
|
||||
curl http://localhost:5000/api
|
||||
# -> dovrebbe restituire "Bank API v1.0.0" come risultato
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Ripensare la gestione dello stato
|
||||
|
||||
Nella [lezione precedente](../../3-data/translations/README.it.md), è stato introdotto un concetto basico di stato nell'app con la variabile globale `account` che contiene i dati bancari per l'utente attualmente connesso. Tuttavia, l'attuale implementazione presenta alcuni difetti. Si provi ad aggiornare la pagina quando ci si trova nella pagina del cruscotto. Che cosa accade?
|
||||
|
||||
Ci sono 3 problemi con il codice corrente:
|
||||
|
||||
- Lo stato non è persistente, poiché un aggiornamento del browser riporta alla pagina di accesso.
|
||||
- Esistono più funzioni che modificano lo stato. Man mano che l'app cresce, può essere difficile tenere traccia delle modifiche ed è facile dimenticare di aggiornarne una.
|
||||
- Lo stato non viene cancellato, quando si fa clic su *Logout* i dati dell'account sono ancora lì anche se si è nella pagina di accesso.
|
||||
|
||||
Si potrebbe aggiornare il codice per affrontare questi problemi uno per uno, ma creerebbe più duplicazioni del codice e renderebbe l'app più complessa e difficile da mantenere. Oppure ci si potrebbe fermare per qualche minuto e ripensare alla strategia.
|
||||
|
||||
> Quali problemi si stanno davvero cercando di risolvere qui?
|
||||
|
||||
La [gestione dello stato](https://en.wikipedia.org/wiki/State_management) consiste nel trovare un buon approccio per risolvere questi due problemi particolari:
|
||||
|
||||
- Come mantenere comprensibile il flusso di dati in un'app?
|
||||
- Come mantenere i dati di stato sempre sincronizzati con l'interfaccia utente (e viceversa)?
|
||||
|
||||
Una volta che ci si è preso cura di questi, qualsiasi altro problema che si potrebbe avere potrebbe essere già stato risolto o essere diventato più facile da sistemare. Ci sono molti possibili approcci per risolvere questi problemi, ma si andrà con una soluzione comune che consiste nel **centralizzare i dati e le modalità per cambiarli**. Il flusso di dati andrebbe così:
|
||||
|
||||

|
||||
|
||||
> Non verrà trattata qui la parte in cui i dati attivano automaticamente l'aggiornamento della vista, poiché è legato a concetti più avanzati di [programmazione reattiva](https://en.wikipedia.org/wiki/Reactive_programming). È un buon argomento da sviluppare successivamente se si è pronti per un'immersione profonda.
|
||||
|
||||
✅ Esistono molte librerie con approcci diversi alla gestione dello stato, [Redux](https://redux.js.org) è un'opzione popolare. Dare un'occhiata ai concetti e ai modelli utilizzati spesso è un buon modo per apprendere quali potenziali problemi si potrebbe dover affrontare nelle grandi app web e come risolverli.
|
||||
|
||||
### Attività
|
||||
|
||||
Si inizierà con un po' di refattorizzazione. Sostituire la dichiarazione di `account` :
|
||||
|
||||
```js
|
||||
let account = null;
|
||||
```
|
||||
|
||||
Con:
|
||||
|
||||
```js
|
||||
let state = {
|
||||
account: null
|
||||
};
|
||||
```
|
||||
|
||||
L'idea è *centralizzare* tutti i dati dell'app in un unico oggetto di stato. Per ora c'è solo `account` nello stato, quindi non cambia molto, ma crea un percorso per le evoluzioni.
|
||||
|
||||
Si devono anche aggiornare le funzioni che lo utilizzano. Nelle funzioni `register()` e `login()` , sostituire `account = ...` con `state.account = ...`;
|
||||
|
||||
Nella parte superiore della funzione `updateDashboard()`, aggiungere questa riga:
|
||||
|
||||
```js
|
||||
const account = state.account;
|
||||
```
|
||||
|
||||
Questa refattorizzazione di per sé non ha portato molti miglioramenti, ma l'idea era di gettare le basi per i prossimi cambiamenti.
|
||||
|
||||
## Tenere traccia delle modifiche ai dati
|
||||
|
||||
Ora che si è impostato l'oggetto `state` per memorizzare i dati, il passaggio successivo è centralizzare gli aggiornamenti. L'obiettivo è rendere più facile tenere traccia di eventuali modifiche e quando si verificano.
|
||||
|
||||
Per evitare che vengano apportate modifiche all'oggetto `state` è anche una buona pratica considerarlo [*immutabile*](https://en.wikipedia.org/wiki/Immutable_object), nel senso che non può essere modificato affatto. Significa anche che si deve creare un nuovo oggetto di stato se si vuole cambiare qualcosa in esso. In questo modo, si crea una protezione [dagli effetti collaterali](https://it.wikipedia.org/wiki/Effetto_collaterale_(informatica)) potenzialmente indesiderati e si aprono possibilità per nuove funzionalità nella propria app come l'implementazione di funzioni di annulla/ripristina, semplificando anche il debug. Ad esempio, è possibile registrare tutte le modifiche apportate allo stato e conservare una cronologia delle modifiche per comprendere l'origine di un bug.
|
||||
|
||||
In JavaScript, si può utilizzare [`Object.freeze()`](https://developer.mozilla.org/it/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) per creare una versione immutabile di un oggetto. Se si prova ad apportare modifiche a un oggetto immutabile, verrà sollevata un'eccezione.
|
||||
|
||||
✅ Si conosce la differenza tra un oggetto *shallow* e uno *deep* immutabile? Si può leggere [qui](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze#What_is_shallow_freeze) per saperne di più.
|
||||
|
||||
### Attività
|
||||
|
||||
Creare una nuova funzione `updateState()` :
|
||||
|
||||
```js
|
||||
function updateState(property, newData) {
|
||||
state = Object.freeze({
|
||||
...state,
|
||||
[property]: newData
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
In questa funzione, si crea un nuovo oggetto di stato e si copiano i dati dallo stato precedente utilizzando l' [*operatore spread (`...`)*](https://developer.mozilla.org/it/docs/Web/JavaScript/Reference/Operators/Spread_syntax). Quindi si sovrascrive una particolare proprietà dell'oggetto state con i nuovi dati usando la [notazione tra parentesi quadre](https://developer.mozilla.org/docs/Web/JavaScript/Guide/Working_with_Objects#Objects_and_properties) `[property]` per l'assegnazione. Infine, si blocca l'oggetto per impedire modifiche utilizzando `Object.freeze()`. Per ora si ha solo la proprietà `account` memorizzata nello stato, ma con questo approccio si possono aggiungere tutte le proprietà che servono nello stato.
|
||||
|
||||
Si aggiornerà anche l'inizializzazione di `state` per assicurarsi che anche lo stato iniziale sia congelato:
|
||||
|
||||
```js
|
||||
let state = Object.freeze({
|
||||
account: null
|
||||
});
|
||||
```
|
||||
|
||||
Successivamente, aggiornare la funzione `register` sostituendo l'istruzione `state.account = result;` con:
|
||||
|
||||
```js
|
||||
updateState('account', result);
|
||||
```
|
||||
|
||||
Fare lo stesso con la funzione `login` , sostituendo `state.account = data;` con:
|
||||
|
||||
```js
|
||||
updateState('account', data);
|
||||
```
|
||||
|
||||
Si coglie ora l'occasione per risolvere il problema della mancata cancellazione dei dati dell'account quando l'utente fa clic su *Logout*.
|
||||
|
||||
Creare una nuova funzione `logout ()`:
|
||||
|
||||
```js
|
||||
function logout() {
|
||||
updateState('account', null);
|
||||
navigate('/login');
|
||||
}
|
||||
```
|
||||
|
||||
In `updateDashboard()`, sostituire il reindirizzamento `return navigate('/ login');` con `return logout()`;
|
||||
|
||||
Provare a registrare un nuovo account, a disconnettersi e ad accedere nuovamente per verificare che tutto funzioni ancora correttamente.
|
||||
|
||||
> Suggerimento: si può dare un'occhiata a tutti i cambiamenti di stato aggiungendo `console.log (state)` nella parte inferiore di `updateState()` e aprendo la console negli strumenti di sviluppo del browser.
|
||||
|
||||
## Persistere lo stato
|
||||
|
||||
La maggior parte delle app web deve conservare i dati per poter funzionare correttamente. Tutti i dati critici vengono solitamente archiviati su un database e accessibili tramite un'API del server, come nel nostro caso i dati dell'account utente. Ma a volte è anche interessante mantenere alcuni dati sulla parte client dell'app in esecuzione nel browser, per una migliore esperienza utente o per migliorare le prestazioni di caricamento.
|
||||
|
||||
Quando si vuole mantenere i dati nel browser, ci sono alcune domande importanti da porsi:
|
||||
|
||||
- *I dati sono sensibili?* Si dovrebbe evitare di memorizzare dati sensibili sul client, come le password degli utenti.
|
||||
- *Per quanto tempo si ha bisogno di conservare questi dati?* Si prevede di accedere a questi dati solo per la sessione corrente o si desidera che vengano memorizzati per sempre?
|
||||
|
||||
Esistono diversi modi per archiviare le informazioni all'interno di un'app web, a seconda di ciò che si desidera ottenere. Ad esempio, si possono utilizzare gli URL per memorizzare una interrogazione di ricerca e renderla condivisibile tra gli utenti. Si possono anche utilizzare i [cookie HTTP](https://developer.mozilla.org/it/docs/Web/HTTP/Cookies) se i dati devono essere condivisi con il server, come le informazioni di [autenticazione](https://it.wikipedia.org/wiki/Autenticazione) .
|
||||
|
||||
Un'altra opzione è utilizzare una delle tante API del browser per la memorizzazione dei dati. Due di loro sono particolarmente interessanti:
|
||||
|
||||
- [`localStorage`](https://developer.mozilla.org/it/docs/Web/API/Window/localStorage): un [archivio chiave/valore](https://en.wikipedia.org/wiki/Key%E2%80%93value_database) che consente di persistere i dati specifici del sito Web corrente in diverse sessioni. I dati salvati in esso non scadono mai.
|
||||
- [`sessionStorage`](https://developer.mozilla.org/it/docs/Web/API/Window/sessionStorage): funziona come `localStorage` tranne per il fatto che i dati in esso memorizzati vengono cancellati al termine della sessione (alla chiusura del browser).
|
||||
|
||||
Notare che entrambe queste API consentono solo di memorizzare [stringhe](https://developer.mozilla.org/it/docs/Web/JavaScript/Reference/Global_Objects/String). Se si desidera archiviare oggetti complessi, si dovranno serializzare nel formato [JSON](https://developer.mozilla.org/it/docs/Web/JavaScript/Reference/Global_Objects/JSON) utilizzando [`JSON.stringify()`](https://developer.mozilla.org/it/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify).
|
||||
|
||||
✅ Se si desidera creare un'app web che non funziona con un server, è anche possibile creare un database sul client utilizzando l' [`API` IndexedDB](https://developer.mozilla.org/it/docs/Web/API/IndexedDB_API). Questo è riservato a casi d'uso avanzati o se è necessario archiviare una quantità significativa di dati, poiché è più complesso da usare.
|
||||
|
||||
### Attività
|
||||
|
||||
Si vuole che gli utenti rimangano collegati fino a quando non fanno clic esplicitamente sul pulsante *Logout* , quindi si utilizzerà `localStorage` per memorizzare i dati dell'account. Per prima cosa, si definisce una chiave che verrà usata per memorizzare i dati.
|
||||
|
||||
```js
|
||||
const storageKey = 'savedAccount';
|
||||
```
|
||||
|
||||
Aggiungere quindi questa riga alla fine della funzione `updateState()`:
|
||||
|
||||
```js
|
||||
localStorage.setItem(storageKey, JSON.stringify(state.account));
|
||||
```
|
||||
|
||||
Con questo, i dati dell'account utente verranno mantenuti e sempre aggiornati poiché si sono centralizzati in precedenza tutti gli aggiornamenti di stato. È qui che si inizia a trarre vantaggio da tutte le rifattorizzazioni precedenti 🙂.
|
||||
|
||||
Poiché i dati vengono salvati, ci si deve anche occupare di ripristinarli quando l'app viene caricata. Dato che si inizierà ad avere più codice di inizializzazione, potrebbe essere una buona idea creare una nuova funzione di inizializzazione `init` , che includa anche il codice precedente nella parte inferiore di `app.js`:
|
||||
|
||||
```js
|
||||
function init() {
|
||||
const savedAccount = localStorage.getItem(storageKey);
|
||||
if (savedAccount) {
|
||||
updateState('account', JSON.parse(savedAccount));
|
||||
}
|
||||
|
||||
// Il codice di inizializzazione precedente
|
||||
window.onpopstate = () => updateRoute();
|
||||
updateRoute();
|
||||
}
|
||||
|
||||
init();
|
||||
```
|
||||
|
||||
Qui si recuperano i dati salvati e, se ce ne sono, si aggiorna lo stato di conseguenza. È importante farlo *prima* di aggiornare la rotta, poiché potrebbe esserci del codice che si basa sullo stato durante l'aggiornamento della pagina.
|
||||
|
||||
Si può anche rendere la pagina del *cruscotto* la pagina predefinita dell'applicazione, poiché ora si sta persistendo i dati dell'account. Se non vengono trovati dati, il cruscotto si occupa comunque di reindirizzare alla pagina di *Login* . In `updateRoute()`, sostituire le istruzioni di contingenza `return navigate ('/login');` con `return navigate ('/dashboard') ;`.
|
||||
|
||||
Ora accedere all'app e provare ad aggiornare la pagina, si dovrebbe rimanere sul cruscotto. Con quell'aggiornamento ci si è presi cura di tutti i problemi iniziali...
|
||||
|
||||
## Aggiornare i dati
|
||||
|
||||
...Si potrebbe uttavia anche averne creato uno nuovo. Oops!
|
||||
|
||||
Andare al cruscotto utilizzando l'account `test`, quindi eseguire questo comando su un terminale per creare una nuova transazione:
|
||||
|
||||
```sh
|
||||
curl --request POST \
|
||||
--header "Content-Type: application/json" \
|
||||
--data "{ \"date\": \"2020-07-24\", \"object\": \"Bought book\", \"amount\": -20 }" \
|
||||
http://localhost:5000/api/accounts/test/transactions
|
||||
```
|
||||
|
||||
Provare subito ad aggiornare la pagina del cruscotto nel browser. Che cosa accade? Si vede la nuova transazione?
|
||||
|
||||
Lo stato viene mantenuto indefinitamente grazie a `localStorage`, ma ciò significa anche che non viene aggiornato fino a quando non si esce dall'app e si accede di nuovo!
|
||||
|
||||
Una possibile strategia per risolvere questo problema è ricaricare i dati dell'account ogni volta che viene caricato il cruscotto, per evitare lo stallo dei dati.
|
||||
|
||||
### Attività
|
||||
|
||||
Creare una nuova funzione `updateAccountData`:
|
||||
|
||||
```js
|
||||
async function updateAccountData() {
|
||||
const account = state.account;
|
||||
if (!account) {
|
||||
return logout();
|
||||
}
|
||||
|
||||
const data = await getAccount(account.user);
|
||||
if (data.error) {
|
||||
return logout();
|
||||
}
|
||||
|
||||
updateState('account', data);
|
||||
}
|
||||
```
|
||||
|
||||
Questo metodo controlla che si sia attualmente collegati, quindi ricarica i dati dell'account dal server.
|
||||
|
||||
Creare un'altra funzione chiamata `refresh`:
|
||||
|
||||
```js
|
||||
async function refresh() {
|
||||
await updateAccountData();
|
||||
updateDashboard();
|
||||
}
|
||||
```
|
||||
|
||||
Questa aggiorna i dati dell'account, quindi si occupa dell'aggiornamento dell'HTML della pagina del cruscotto. È ciò che si deve chiamare quando viene caricata la rotta del cruscotto (dashboard). Aggiornare la definizione del percorso con:
|
||||
|
||||
```js
|
||||
const routes = {
|
||||
'/login': { templateId: 'login' },
|
||||
'/dashboard': { templateId: 'dashboard', init: refresh }
|
||||
};
|
||||
```
|
||||
|
||||
Provare a ricaricare il cruscotto ora, dovrebbe visualizzare i dati dell'account aggiornati.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Sfida
|
||||
|
||||
Ora che i dati dell'account vengono ricaricati ogni volta che viene caricato il cruscotto, si pensa che sia ancora necessario persistere *tutti i dati dell'account* ?
|
||||
|
||||
Provare a lavorare insieme per cambiare ciò che viene salvato e caricato da `localStorage` per includere solo ciò che è assolutamente necessario per il funzionamento dell'app.
|
||||
|
||||
## Quiz Post-Lezione
|
||||
|
||||
[Quiz post-lezione](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/48?loc=it)
|
||||
|
||||
## Compito
|
||||
|
||||
[Implementare la finestra di dialogo "Aggiungi transazione"](assignment.it.md)
|
||||
|
||||
Ecco un esempio di risultato dopo aver completato il compito:
|
||||
|
||||

|
@@ -1,281 +0,0 @@
|
||||
# バンキングアプリを作ろう その 4: 状態管理の概念
|
||||
|
||||
## レッスン前の小テスト
|
||||
|
||||
[レッスン前の小テスト](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/47?loc=ja)
|
||||
|
||||
### イントロダクション
|
||||
|
||||
Web アプリケーションが成長するにつれて、すべてのデータの流れを追跡することが難しくなります。どのコードがデータを取得し、どのページがデータを消費し、どこでいつ更新する必要があるのか...メンテナンスが難しい厄介なコードになってしまいがちです。これは、ユーザーデータなど、アプリの異なるページ間でデータを共有する必要がある場合に特に当てはまります。*状態管理*の概念は、あらゆる種類のプログラムに常に存在していますが、Web アプリの複雑さが増すにつれ、開発中に考えるべき重要なポイントになってきています。
|
||||
|
||||
この最後のパートでは、状態の管理方法を再考するために構築したアプリを見ていきます。任意の時点でのブラウザの更新をサポートし、ユーザーセッション間でのデータの永続化を可能にします。
|
||||
|
||||
### 前提条件
|
||||
|
||||
このレッスンでは、Web アプリの[データ取得](../../3-data/translations/README.ja.md)の部分が完了している必要があります。また、アカウントデータを管理するためには、ローカルに [Node.js](https://nodejs.org/ja) をインストールし、[サーバー API を実行する](../../api/translations/README.ja.md)をインストールする必要があります。
|
||||
|
||||
ターミナルでこのコマンドを実行することで、サーバーが正常に動作しているかどうかをテストすることができます。
|
||||
|
||||
```sh
|
||||
curl http://localhost:5000/api
|
||||
# -> 結果として "Bank API v1.0.0" を返す必要があります。
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 状態管理を再考する
|
||||
|
||||
[前回のレッスン](../../3-data/translations/README.ja.md)では、現在ログインしているユーザーの銀行データを含むグローバル変数 `account` を使って、アプリの基本的な状態の概念を紹介しました。しかし、現在の実装にはいくつかの欠陥があります。ダッシュボード上でページをリフレッシュしてみてください。何が起こるのでしょうか?
|
||||
|
||||
現在のコードには3つの問題があります。
|
||||
|
||||
- ブラウザをリフレッシュするとログインページに戻るため、状態は保持されません
|
||||
- 状態を変更する関数が複数あります。アプリが大きくなると、変更を追跡するのが難しくなり、1つの更新を忘れがちになります
|
||||
- 状態が片付かず、*Logout* をクリックしてもログインページになってもアカウントデータが残っています
|
||||
|
||||
これらの問題に一つずつ対処するためにコードを更新することもできますが、コードの重複が多くなり、アプリがより複雑になり、メンテナンスが難しくなります。あるいは、数分間小休止して、戦略を再考することもできます。
|
||||
|
||||
> ここで本当に解決しようとしている問題は何か?
|
||||
|
||||
[状態管理](https://en.wikipedia.org/wiki/State_management)は、この2つの特定の問題を解決するための良いアプローチを見つけることがすべてです。
|
||||
|
||||
- アプリ内のデータフローをわかりやすく保つには?
|
||||
- アプリ内のデータフローを理解しやすい状態に保つには?
|
||||
|
||||
これらの問題を解決したら、他の問題はすでに解決されているか、簡単に解決できるようになっているかもしれません。これらの問題を解決するための多くの可能なアプローチがありますが、ここでは、**データとそれを変更する方法**を集中化することで構成される一般的な解決策を採用します。データの流れは次のようになります。
|
||||
|
||||

|
||||
|
||||
> ここでは、データが自動的にビューの更新のトリガーとなる部分は、[Reactive Programming](https://en.wikipedia.org/wiki/Reactive_programming)のより高度な概念に関連しているので、ここでは取り上げません。深く掘り下げたい方には良いフォローアップテーマになるでしょう。
|
||||
|
||||
✅ 状態管理へのさまざまなアプローチを持つライブラリはたくさんありますが、[Redux](https://redux.js.org) は人気のあるオプションです。大規模な Web アプリケーションで直面する可能性のある問題や、それをどのように解決できるかを学ぶための良い方法として、使用されている概念やパターンを見てみましょう。
|
||||
|
||||
### タスク
|
||||
|
||||
まずは少しリファクタリングをしてみましょう。`account` 宣言を置換します。
|
||||
|
||||
```js
|
||||
let account = null;
|
||||
```
|
||||
|
||||
このようにします。
|
||||
|
||||
```js
|
||||
let state = {
|
||||
account: null
|
||||
};
|
||||
```
|
||||
|
||||
このアイデアは、単一のステートオブジェクトにすべてのアプリデータを*中央集権化することです。今のところは `account` があるだけなので、あまり変化はありませんが、進化のためのパスを作成します。
|
||||
|
||||
また、これを使って関数を更新しなければなりません。関数 `register()` と `login()` において、`account = ...` を `state.account = ...` に置き換えてください。
|
||||
|
||||
関数 `updateDashboard()` の先頭に以下の行を追加します。
|
||||
|
||||
```js
|
||||
const account = state.account;
|
||||
```
|
||||
|
||||
今回のリファクタリングだけではあまり改善は見られませんでしたが、次の変更のための基礎を固めようと考えたのです。
|
||||
|
||||
## データ変更の追跡
|
||||
|
||||
データを保存するために `state` オブジェクトを配置したので、次のステップは更新を一元化することです。目的は、いつ変更があったのか、いつ変更が発生したのかを簡単に把握できるようにすることです。
|
||||
|
||||
`state` オブジェクトに変更が加えられないようにするためには、`state` オブジェクトを [*immutable*](https://en.wikipedia.org/wiki/Immutable_object) と考えるのも良い方法です。これはまた、何かを変更したい場合には新しいステートオブジェクトを作成しなければならないことを意味します。このようにすることで、潜在的に望ましくない[副作用](https://ja.wikipedia.org/wiki/%E5%89%AF%E4%BD%9C%E7%94%A8_(%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0))についての保護を構築し、アンドゥ/リドゥの実装のようなアプリの新機能の可能性を開くと同時に、デバッグを容易にします。例えば、ステートに加えられたすべての変更をログに記録し、バグの原因を理解するために変更の履歴を保持することができます。
|
||||
|
||||
JavaScript では、[`Object.freeze()`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) を使って、オブジェクトの不変バージョンを作成することができます。不変オブジェクトに変更を加えようとすると例外が発生します。
|
||||
|
||||
✅ *浅い*不変オブジェクトと*深い*不変オブジェクトの違いを知っていますか? それについては [こちら](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze#What_is_shallow_freeze) を参照してください。
|
||||
|
||||
### タスク
|
||||
|
||||
新しい `updateState()` 関数を作成してみましょう。
|
||||
|
||||
```js
|
||||
function updateState(property, newData) {
|
||||
state = Object.freeze({
|
||||
...state,
|
||||
[property]: newData
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
この関数では、新しいステートオブジェクトを作成し、[*spread (`...`) operator*](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Spread_syntax#spread_in_object_literals) を使用して前のステートからデータをコピーしています。次に、[ブラケット表記](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Working_with_Objects#objects_and_properties) `[property]` を使用して、ステートオブジェクトの特定のプロパティを新しいデータでオーバーライドします。最後に、`Object.freeze()`を使ってオブジェクトをロックし、変更を防ぎます。今のところ、`account` プロパティだけがステートに保存されていますが、この方法では必要なだけのプロパティをステートに追加することができます。
|
||||
|
||||
また、`state` の初期化を更新して初期状態も凍結されるようにします。
|
||||
|
||||
```js
|
||||
let state = Object.freeze({
|
||||
account: null
|
||||
});
|
||||
```
|
||||
|
||||
その後、`state.account = result;` の代入を `state.account = result;` に置き換えて `register` 関数を更新します。
|
||||
|
||||
```js
|
||||
updateState('account', result);
|
||||
```
|
||||
|
||||
同じことを `login` 関数で行い、`state.account = data;` に置き換えます。
|
||||
|
||||
```js
|
||||
updateState('account', data);
|
||||
```
|
||||
|
||||
ここで、ユーザーが *Logout* をクリックしたときにアカウントデータがクリアされない問題を修正します。
|
||||
|
||||
新しい関数 `logout()` を作成します。
|
||||
|
||||
```js
|
||||
function logout() {
|
||||
updateState('account', null);
|
||||
navigate('/login');
|
||||
}
|
||||
```
|
||||
|
||||
`updateDashboard()` で、リダイレクト `return navigate('/login');` を `return logout()` に置き換えてください。
|
||||
|
||||
新しいアカウントを登録して、ログアウトとログインを繰り返してみて、すべてが正常に動作することを確認してください。
|
||||
|
||||
> ヒント: `updateState()` の下部に `console.log(state)` を追加し、ブラウザの開発ツールでコンソールを開くことで、すべての状態の変化を見ることができます。
|
||||
|
||||
## 状態を維持する
|
||||
|
||||
ほとんどの Web アプリは、データを保持しておかないと正常に動作しません。すべての重要なデータは通常、データベースに保存され、サーバー API を介してアクセスされます。しかし、時には、より良いユーザーエクスペリエンスや読み込みパフォーマンスを向上させるために、ブラウザ上で実行されているクライアントアプリのデータを永続化することも興味深いことです。
|
||||
|
||||
ブラウザにデータを永続化する場合、いくつかの重要な質問があります。
|
||||
|
||||
- *データは機密性の高いものでしょうか?* ユーザーパスワードなどの機密性の高いデータをクライアントに保存することは避けるべきです
|
||||
- *このデータをどのくらいの期間保存する必要がありますか?* このデータにアクセスするのは現在のセッションのためだけですか、それとも永遠に保存したいですか?
|
||||
|
||||
Web アプリ内の情報を保存する方法は、目的に応じて複数あります。例えば、URL を使用して検索クエリを保存し、ユーザー間で共有できるようにすることができます。また、[認証](https://en.wikipedia.org/wiki/Authentication)情報のように、データをサーバーと共有する必要がある場合は、[HTTP クッキー](https://developer.mozilla.org/ja/docs/Web/HTTP/Cookies)を使用することもできます。
|
||||
|
||||
もう一つの選択肢は、データを保存するための多くのブラウザ API のうちの一つを使用することです。その中でも特に興味深いものが2つあります。
|
||||
|
||||
- [`localStorage`](https://developer.mozilla.org/ja/docs/Web/API/Window/localStorage): [Key/Value ストア](https://en.wikipedia.org/wiki/Key%E2%80%93value_database)は、異なるセッションにまたがって現在の Web サイトに固有のデータを永続化することができます。保存されたデータは期限切れになることはありません
|
||||
- [`sessionStorage`](https://developer.mozilla.org/ja/docs/Web/API/Window/sessionStorage): これは `localStorage` と同じように動作しますが、保存されたデータはセッションの終了時(ブラウザが閉じられた時)に消去されます
|
||||
|
||||
これらの API はどちらも[文字列](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String)しか保存できないことに注意してください。複雑なオブジェクトを格納したい場合は、[`JSON.stringify()`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) を使って [JSON](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/JSON) 形式にシリアライズする必要があります。
|
||||
|
||||
✅ サーバーで動作しない Web アプリを作成したい場合は、[`IndexedDB` API](https://developer.mozilla.org/ja/docs/Web/API/IndexedDB_API) を使用してクライアント上にデータベースを作成することも可能です。これは高度なユースケースや、かなりの量のデータを保存する必要がある場合には、使用するのがより複雑になるため、予約されています。
|
||||
|
||||
### タスク
|
||||
|
||||
ユーザーが明示的に *Logout* ボタンをクリックするまでログインしたままにしたいので、`localStorage` を使ってアカウントデータを保存します。まず、データを保存するためのキーを定義しましょう。
|
||||
|
||||
```js
|
||||
const storageKey = 'savedAccount';
|
||||
```
|
||||
|
||||
そして、この行を `updateState()` 関数の最後に追加します。
|
||||
|
||||
```js
|
||||
localStorage.setItem(storageKey, JSON.stringify(state.account));
|
||||
```
|
||||
|
||||
これで、以前はすべての状態の更新を一元化していたので、ユーザーアカウントのデータは永続化され、常に最新の状態になります。ここからが、以前のすべてのリファクタリングの恩恵を受け始めるところです 🙂。
|
||||
|
||||
データが保存されているので、アプリが読み込まれたときに復元することにも気を配らなければなりません。初期化コードが増えてくるので、`app.js` の下部に以前のコードも含めた `init` 関数を新たに作成しておくといいかもしれません。
|
||||
|
||||
```js
|
||||
function init() {
|
||||
const savedAccount = localStorage.getItem(storageKey);
|
||||
if (savedAccount) {
|
||||
updateState('account', JSON.parse(savedAccount));
|
||||
}
|
||||
|
||||
// 前回の初期化コード
|
||||
window.onpopstate = () => updateRoute();
|
||||
updateRoute();
|
||||
}
|
||||
|
||||
init();
|
||||
```
|
||||
|
||||
ここでは保存されたデータを取得し、もしあればそれに応じて状態を更新します。ページの更新中に状態に依存するコードがあるかもしれないので、ルートを更新する前にこれを行うことが重要です。
|
||||
|
||||
アカウントデータを保持しているので、*ダッシュボード* ページをアプリケーションのデフォルトページにすることもできます。データが見つからない場合は、ダッシュボードが *ログイン* ページにリダイレクトします。`updateRoute()` で、フォールバックの `return navigate('/login');` を `return navigate('dashboard');` に置き換えます。
|
||||
|
||||
アプリでログインしてページを更新してみてください。このアップデートで初期の問題はすべて解決しました。
|
||||
|
||||
## データの更新
|
||||
|
||||
...しかし、我々はまた、新しいものを作ったかもしれません。おっと!
|
||||
|
||||
`test` アカウントを使ってダッシュボードに行き、ターミナルで以下のコマンドを実行して新しいトランザクションを作成します。
|
||||
|
||||
```sh
|
||||
curl --request POST \
|
||||
--header "Content-Type: application/json" \
|
||||
--data "{ \"date\": \"2020-07-24\", \"object\": \"Bought book\", \"amount\": -20 }" \
|
||||
http://localhost:5000/api/accounts/test/transactions
|
||||
```
|
||||
|
||||
ダッシュボードのページをブラウザで更新してみてください。どうなりますか?新しいトランザクションが表示されましたか?
|
||||
|
||||
この状態は `localStorage` のおかげで無期限に保持されますが、アプリからログアウトして再度ログインするまで更新されません。
|
||||
|
||||
これを修正するために考えられる戦略の1つは、ダッシュボードがロードされるたびにアカウントデータをリロードして、データのストールを回避することです。
|
||||
|
||||
### タスク
|
||||
|
||||
新しい関数 `updateAccountData` を作成します。
|
||||
|
||||
```js
|
||||
async function updateAccountData() {
|
||||
const account = state.account;
|
||||
if (!account) {
|
||||
return logout();
|
||||
}
|
||||
|
||||
const data = await getAccount(account.user);
|
||||
if (data.error) {
|
||||
return logout();
|
||||
}
|
||||
|
||||
updateState('account', data);
|
||||
}
|
||||
```
|
||||
|
||||
このメソッドは現在ログインしているかどうかをチェックし、サーバからアカウントデータをリロードします。
|
||||
|
||||
`refresh` という名前の別の関数を作成します。
|
||||
|
||||
```js
|
||||
async function refresh() {
|
||||
await updateAccountData();
|
||||
updateDashboard();
|
||||
}
|
||||
```
|
||||
|
||||
これはアカウントデータを更新し、ダッシュボードページの HTML を更新する処理を行います。ダッシュボードルートがロードされたときに呼び出す必要があるものです。これでルート定義を更新します。
|
||||
|
||||
```js
|
||||
const routes = {
|
||||
'/login': { templateId: 'login' },
|
||||
'/dashboard': { templateId: 'dashboard', init: refresh }
|
||||
};
|
||||
```
|
||||
|
||||
ダッシュボードをリロードしてみると、更新されたアカウントデータが表示されるはずです。
|
||||
|
||||
---
|
||||
|
||||
## 🚀 チャレンジ
|
||||
|
||||
ダッシュボードがロードされるたびにアカウントデータをリロードするようになりましたが、すべてのアカウントデータを保持する必要があると思いますか?
|
||||
|
||||
アプリが動作するために絶対に必要なものだけを含むように、`localStorage` から保存およびロードされるものを変更するために、一緒に作業してみてください。
|
||||
|
||||
## レッスン後の小テスト
|
||||
|
||||
[レッスン後の小テスト](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/48?loc=ja)
|
||||
|
||||
## 課題
|
||||
|
||||
[「トランザクションの追加」ダイアログの実装](assignment.ja.md)
|
||||
|
||||
課題を終えた後の結果の一例です。
|
||||
|
||||

|
@@ -1,281 +0,0 @@
|
||||
# 은행 앱 제작하기 파트 4: 상태 관리의 컨셉
|
||||
|
||||
## 강의 전 퀴즈
|
||||
|
||||
[Pre-lecture quiz](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/47?loc=ko)
|
||||
|
||||
### 소개
|
||||
|
||||
웹 애플리케이션이 커지면서, 모든 데이터 흐름을 추적하는 것은 어렵습니다. 어떤 코드가 데이터를 가져오고, 어떤 페이지가 데이터를 사용하고, 언제 어디서 갱신해야 하는지... 관리하기 어려운 복잡한 코드로 끝날 수 있습니다. 이는 앱의 여러 페이지가 서로 데이터를 공유하는 경우에 특히 더 그렇습니다, 예시로 사용자 데이터. *상태 관리*의 컨셉은 항상 모든 종류의 프로그램에 존재했지만, 웹 앱이 계속 복잡해지면서 이제는 개발하면서 고려해야 할 키 포인트가 되었습니다.
|
||||
|
||||
이 최종 부분에서는, 상태 관리하는 방법을 다시 생각해보며, 언제든 브라우저 새로고침을 지원하고, 사용자 세션에서 데이터를 유지하기 위해서 작성한 앱을 살펴 보겠습니다.
|
||||
|
||||
### 준비물
|
||||
|
||||
이 강의의 웹 앱 [data fetching](../../3-data/translations/README.ko.md) 파트를 완료해둬야 합니다. [Node.js](https://nodejs.org)와 [run the server API](../../api/README.md)를 로컬에 설치해야 계정 정보를 관리할 수 있습니다.
|
||||
|
||||
터미널에서 다음 명령을 수행하여 서버가 잘 실행되고 있는지 테스트할 수 있습니다:
|
||||
|
||||
```sh
|
||||
curl http://localhost:5000/api
|
||||
# -> should return "Bank API v1.0.0" as a result
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 상태 관리에 대하여 다시 생각하기
|
||||
|
||||
[이전 강의](../../3-data/translations/README.ko.md)에서는, 현재 로그인한 사용자의 은행 데이터를 포함하는 전역 `account` 변수를 사용하여 앱에 기초 상태 개념을 도입했습니다. 그러나, 현재 구현에는 조금 취약점이 있습니다. 대시보드에서 페이지를 새로 고쳐보기 바랍니다. 무슨 일이 일어나고 있나요?
|
||||
|
||||
현재 코드에는 3가지 이슈가 있습니다:
|
||||
|
||||
- 브라우저를 새로 고치면 로그인 페이지로 돌아가기 때문에, 상태가 유지되지 않습니다.
|
||||
- 상태를 바꾸는 여러 함수들이 있습니다. 앱이 커지면서, 변경점을 추적하기 어렵고 갱신한 것을 잊어버리기 쉽습니다.
|
||||
- 상태가 정리되지 않았습니다, *로그아웃*을 클릭하면 로그인 페이지에 있어도 계정 데이터가 그대로 유지됩니다.
|
||||
|
||||
이런 이슈를 하나씩 해결하기 위해 코드를 갱신할 수는 있지만, 코드 중복이 더 많이 발생되고 앱이 더 복잡해져서 유지 관리가 어려워집니다. 또는 몇 분 동안 잠시 멈춰서 다시 기획할 수도 있습니다.
|
||||
|
||||
> 여기서 우리가 실제로 해결할 문제는 무엇인가요?
|
||||
|
||||
[State management](https://en.wikipedia.org/wiki/State_management)는 다음 2가지 특정한 문제를 해결하기 위해 좋은 접근 방식을 찾습니다:
|
||||
|
||||
- 이해하기 쉽게 앱의 데이터 흐름을 유지하는 방법은 무엇인가요?
|
||||
- 상태 데이터를 사용자 인터페이스와 항상 동기화하는 방법은 있나요 (혹은 그 반대로)?
|
||||
|
||||
이런 문제를 해결한 후에는 다른 이슈가 이미 고쳐졌거나 더 쉽게 고칠 수 있습니다. 이러한 문제를 해결하기 위한 여러 가능한 방식들이 있지만, **데이터를 중앙 집중화하고 변경하는 방법**으로 구성된 공통 솔루션을 사용합니다. 데이터 흐름은 다음과 같습니다:
|
||||
|
||||

|
||||
|
||||
> 데이터와 뷰 갱신을 자동으로 연결하는 부분은, [Reactive Programming](https://en.wikipedia.org/wiki/Reactive_programming)의 고급 컨셉과 연결되었으므로 여기서 다루지는 않습니다. 깊게 분석한다면 좋게 팔로우 업할 주제입니다.
|
||||
|
||||
✅ 상태 관리에 대한 접근 방식이 다른 수 많은 라이브러리가 있으며, [Redux](https://redux.js.org)는 인기있는 옵션입니다. 큰 웹 앱에서 마주할 수 있는 잠재적 이슈와 해결 방식을 알 수 있으므로 사용된 컨셉과 패턴을 살펴보세요.
|
||||
|
||||
### 작업
|
||||
|
||||
조금 리팩토링을 해보면서 시작해봅니다. `account` 선언을 바꿉니다:
|
||||
|
||||
```js
|
||||
let account = null;
|
||||
```
|
||||
|
||||
With:
|
||||
|
||||
```js
|
||||
let state = {
|
||||
account: null
|
||||
};
|
||||
```
|
||||
|
||||
이 아이디어는 모든 앱 데이터를 단일 상태 개체에서 *중앙에 모으는* 것 입니다. 현재 상태에서는 `account`만 가지고 있으므로 많이 변하지 않지만, 발전을 위한 길을 닦아둡니다.
|
||||
|
||||
또한 그것을 사용하여 함수를 갱신해야 합니다. `register()`와 `login()` 함수에서, `account = ...`를 `state.account = ...`로 바꿉니다.
|
||||
|
||||
`updateDashboard()` 함수 상단에, 이 줄을 추가합니다:
|
||||
|
||||
```js
|
||||
const account = state.account;
|
||||
```
|
||||
|
||||
이 리팩토링만으로는 많은 개선이 이루어지지 않지만, 아이디어는 다음 변경점의 토대를 마련해줍니다.
|
||||
|
||||
## 데이터 변경 추적하기
|
||||
|
||||
데이터로 저장할 `state` 객체를 두었으므로, 다음 단계는 갱신 작업을 중앙 집중화하는 것입니다. 목표는 모든 변경점과 발생 시점을 쉽게 추적하는 것입니다.
|
||||
|
||||
`state` 객체가 변경되지 않으려면, [*immutable*](https://en.wikipedia.org/wiki/Immutable_object)한 것으로 간주하는 것이 좋습니다. 즉, 전혀 수정할 수 없다는 점을 의미합니다. 또한 변경하려는 경우에는 새로운 상태 객체를 만들어야 된다는 점을 의미합니다. 이렇게 하면, 잠재적으로 원하지 않는 [side effects](https://en.wikipedia.org/wiki/Side_effect_(computer_science))에 보호하도록 만들고, undo/redo를 구현하는 것 처럼 앱의 새로운 기능에 대한 가능성을 열어 디버깅을 더 쉽게 만듭니다. 예를 들자면, 상태에 대한 모든 변경점을 남기고 유지하여 버그의 원인을 파악할 수 있습니다.
|
||||
|
||||
JavaScript에서, [`Object.freeze()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze)를 사용하여 변경할 수 없는 버전의 객체를 만들 수 있습니다. 변경 불가능한 객체를 바꾸려고 하면 예외가 발생합니다.
|
||||
|
||||
✅ *shallow*와 *deep* 불변 객체의 차이점을 알고 계시나요? [here](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze#What_is_shallow_freeze)에서 읽을 수 있습니다.
|
||||
|
||||
### 작업
|
||||
|
||||
새로운 `updateState()` 함수를 만듭니다:
|
||||
|
||||
```js
|
||||
function updateState(property, newData) {
|
||||
state = Object.freeze({
|
||||
...state,
|
||||
[property]: newData
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
이 함수에서는, 새로운 상태 객체를 만들고 [*spread (`...`) operator*](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/Spread_syntax#Spread_in_object_literals)로 이전 상태의 데이터를 복사합니다. 그러고 할당을 위해 [bracket notation](https://developer.mozilla.org/docs/Web/JavaScript/Guide/Working_with_Objects#Objects_and_properties) `[property]`를 사용하여 상태 객체의 특정한 속성을 새로운 데이터로 다시 정의합니다. 최종적으로, 변경되는 것을 막기 위해 `Object.freeze()`를 사용하여 객체를 잠급니다. 지금 상태에는 `account` 속성만 저장되어 있지만, 이 접근 방식으로 상태에 필요한 순간마다 많은 속성들을 추가할 수 있습니다.
|
||||
|
||||
또한 초기 상태가 동결되도록 `state` 초기화 작업도 갱신합니다:
|
||||
|
||||
```js
|
||||
let state = Object.freeze({
|
||||
account: null
|
||||
});
|
||||
```
|
||||
|
||||
그런 다음, `state.account = result;` 할당을 이 것으로 대체하여 `register` 함수를 갱신합니다:
|
||||
|
||||
```js
|
||||
updateState('account', result);
|
||||
```
|
||||
|
||||
`login` 함수에서도 동일하게 진행하고, `state.account = data;`도 이 것으로 바꿉니다:
|
||||
|
||||
```js
|
||||
updateState('account', data);
|
||||
```
|
||||
|
||||
이제 사용자가 *Logout*을 클릭 할 때 계정 데이터가 지워지지 않는 이슈를 해결할 수 있습니다.
|
||||
|
||||
새로운 함수 `logout()`을 만듭니다:
|
||||
|
||||
```js
|
||||
function logout() {
|
||||
updateState('account', null);
|
||||
navigate('/login');
|
||||
}
|
||||
```
|
||||
|
||||
`updateDashboard()` 에서, 리다이렉션하는 `return navigate('/login');`을 `return logout()`으로 바꿉니다;
|
||||
|
||||
새로운 계정으로 가입을 시도하면, 로그아웃하고 다시 로그인하여 모두 잘 작동하는지 확인합니다.
|
||||
|
||||
> Tip: `updateState()` 하단에 `console.log(state)`를 추가하고 브라우저의 개발 도구에서 콘솔을 열면 모든 상태 변경점을 볼 수 있습니다.
|
||||
|
||||
## 상태 유지하기
|
||||
|
||||
대부분 웹 앱이 잘 작동하려면 데이터를 유지할 필요가 있습니다. 모든 중요한 데이터는 일반적으로 데이터베이스에 저장되고 우리 케이스에는 사용자 계정 데이터처럼, 서버 API를 통해 접근됩니다. 그러나 때로는, 더 좋은 사용자 경험이나 로딩 퍼포먼스를 개선하기 위해서, 브라우저에서 실행중인 클라이언트 앱에 일부 데이터를 유지하는 것도 흥미롭습니다.
|
||||
|
||||
브라우저에서 데이터를 유지하려면, 스스로에게 몇 중요한 질문을 해야합니다:
|
||||
|
||||
- *민감한 데이터인가요?* 사용자 암호와 같은, 민감한 데이터는 클라이언트에 저장하지 않아야 합니다.
|
||||
- *데이터를 얼마나 오래 보관해야 하나요?* 현재 세션에서만 데이터에 접근하거나 계속 저장할 계획인가요?
|
||||
|
||||
달성하려는 목표에 따라, 웹 앱 안에서 정보를 저장하는 방법에는 여러 가지가 있습니다. 예를 들면, URL을 사용하여 검색 쿼리를 저장하고, 사용자끼리 공유할 수 있습니다. [authentication](https://en.wikipedia.org/wiki/Authentication) 정보처럼, 데이터를 서버와 공유해야하는 경우에도 [HTTP cookies](https://developer.mozilla.org/docs/Web/HTTP/Cookies)를 사용할 수 있습니다.
|
||||
|
||||
다른 옵션으로는 데이터 저장을 위해 여러 브라우저 API 중 하나를 사용하는 것입니다. 그 중 2가지가 특히 흥미롭습니다:
|
||||
|
||||
- [`localStorage`](https://developer.mozilla.org/docs/Web/API/Window/localStorage): [Key/Value store](https://en.wikipedia.org/wiki/Key%E2%80%93value_database)는 다른 세션에서 현재 웹 사이트에 대한 특정 데이터를 유지할 수 있습니다. 저장된 데이터는 만료되지 않습니다.
|
||||
- [`sessionStorage`](https://developer.mozilla.org/docs/Web/API/Window/sessionStorage): 이는 세션이 끝날 때(브라우저가 닫힐 때)에 저장된 데이터가 지워진다는 점을 제외하면 `localStorage`와 동일하게 작동합니다.
|
||||
|
||||
이 두 API는 모두 [strings](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)만 저장할 수 있습니다. 복잡한 객체를 저장하려면, [`JSON.stringify()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify)를 사용하여 [JSON](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/JSON) 포맷으로 직렬화해야 합니다.
|
||||
|
||||
✅ 서버에서 동작하지 않는 웹 앱을 만드려면, [`IndexedDB` API](https://developer.mozilla.org/docs/Web/API/IndexedDB_API)로 클라이언트에 데이터베이스를 만들 수도 있습니다. 이는 고급 사용 케이스이거나, 사용하기 복잡한 많은 양의 데이터를 저장해야 할 때에 사용하도록 되어있습니다.
|
||||
|
||||
### 작업
|
||||
|
||||
*Logout* 버튼을 명시적으로 클릭할 때까지 로그인 상태가 유지되기를 원하므로, `localStorage`로 계정 데이터를 저장합니다. 먼저, 데이터를 저장하는 데 사용할 키를 정의하겠습니다.
|
||||
|
||||
```js
|
||||
const storageKey = 'savedAccount';
|
||||
```
|
||||
|
||||
그러고 `updateState()` 함수의 하단에 이 줄을 추가합니다:
|
||||
|
||||
```js
|
||||
localStorage.setItem(storageKey, JSON.stringify(state.account));
|
||||
```
|
||||
|
||||
이를 통해, 이전의 모든 상태를 갱신하는 작업이 가운데로 모임에 따라 사용자 계정 데이터가 유지되고 항상 최신-상태를 유지합니다. 이것으로 이전 모든 리팩터링 작업의 혜택을 받기 시작했습니다 🙂.
|
||||
|
||||
더 많은 초기화 코드를 가지게 될 예정이므로 새로운 `init` 함수를 만드는 것이 좋습니다, 여기에는 `app.js`의 하단에 이전 코드가 포함됩니다:
|
||||
|
||||
```js
|
||||
function init() {
|
||||
const savedAccount = localStorage.getItem(storageKey);
|
||||
if (savedAccount) {
|
||||
updateState('account', JSON.parse(savedAccount));
|
||||
}
|
||||
|
||||
// Our previous initialization code
|
||||
window.onpopstate = () => updateRoute();
|
||||
updateRoute();
|
||||
}
|
||||
|
||||
init();
|
||||
```
|
||||
|
||||
여기에서 저장된 데이터를 검색하고, 그에 따라서 상태를 갱신합니다. 페이지를 갱신하다가 상태에 의존하는 코드가 있을 수 있으므로, 라우터를 갱신하기 *전에* 하는 것이 중요합니다.
|
||||
|
||||
이제 계정 데이터를 유지하고 있으므로, *대시보드* 페이지를 애플리케이션 기본 페이지로 만들 수도 있습니다. 데이터가 없다면, 대시보드는 언제나 *로그인* 페이지로 리다이렉팅합니다. `updateRoute ()`에서, `return navigate('/login');`을 `return navigate('dashboard');`로 바꿉니다.
|
||||
|
||||
이제 앱에 로그인하고 페이지를 새로 고쳐보면, 대시보드에 남아있어야 합니다. 이 업데이트로 모든 초기 이슈를 처리했습니다...
|
||||
|
||||
## 데이터 새로 고치기
|
||||
|
||||
...그러나 새로운 것을 만들 수도 있습니다. 웁스!
|
||||
|
||||
`test` 계정을 사용하여 대시보드로 이동하면, 터미널에서 이 명령을 실행하여 새로운 트랜잭션을 만듭니다:
|
||||
|
||||
```sh
|
||||
curl --request POST \
|
||||
--header "Content-Type: application/json" \
|
||||
--data "{ \"date\": \"2020-07-24\", \"object\": \"Bought book\", \"amount\": -20 }" \
|
||||
http://localhost:5000/api/accounts/test/transactions
|
||||
```
|
||||
|
||||
지금 브라우저에서 대시보드 페이지를 새로 고쳐봅니다. 어떤 일이 일어났나요? 새로운 트랜잭션이 보이나요?
|
||||
|
||||
상태는 `localStorage` 덕분에 무한으로 유지하지만, 앱에서 로그아웃하고 다시 로그인할 때까지 갱신하지 않는다는 점을 의미합니다!
|
||||
|
||||
해결할 수 있는 한 가지 전략은 대시보드를 불러올 때마다 계정 데이터를 다시 불러와서, 데이터가 오래되는 현상을 방지하는 것 입니다.
|
||||
|
||||
### 작업
|
||||
|
||||
새로운 함수 `updateAccountData`를 만듭니다:
|
||||
|
||||
```js
|
||||
async function updateAccountData() {
|
||||
const account = state.account;
|
||||
if (!account) {
|
||||
return logout();
|
||||
}
|
||||
|
||||
const data = await getAccount(account.user);
|
||||
if (data.error) {
|
||||
return logout();
|
||||
}
|
||||
|
||||
updateState('account', data);
|
||||
}
|
||||
```
|
||||
|
||||
이 메소드는 현재 로그인되어 있는지 본 다음에 서버에서 계정 데이터를 다시 불러옵니다.
|
||||
|
||||
`refresh`라고 이름을 지은 또 다른 함수를 만듭니다:
|
||||
|
||||
```js
|
||||
async function refresh() {
|
||||
await updateAccountData();
|
||||
updateDashboard();
|
||||
}
|
||||
```
|
||||
|
||||
이는 계정 데이터를 갱신하고나서, 대시보드 페이지의 HTML도 갱신하게 됩니다. 대시보드 라우터를 불러올 때마다 호출해야 합니다. 다음으로 라우터 정의를 갱신합니다:
|
||||
|
||||
```js
|
||||
const routes = {
|
||||
'/login': { templateId: 'login' },
|
||||
'/dashboard': { templateId: 'dashboard', init: refresh }
|
||||
};
|
||||
```
|
||||
|
||||
지금 대시보드를 다시 불러옵니다, 갱신된 계정 데이터를 볼 수 있어야 합니다.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 도전
|
||||
|
||||
이제 대시보드를 불러올 때마다 계정 데이터가 다시 불러와지는데, 여전히 *모든 계정* 데이터를 유지해야 된다고 생각하나요?
|
||||
|
||||
앱이 동작하는 데 꼭 필요한 것만 있도록 `localStorage` 에 저장하고 불러온 항목을 함께 바꿔봅니다.
|
||||
|
||||
## 강의 후 퀴즈
|
||||
|
||||
[Post-lecture quiz](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/48?loc=ko)
|
||||
|
||||
## 과제
|
||||
|
||||
[Implement "Add transaction" dialog](../assignment.md)
|
||||
|
||||
다음은 과제를 완료한 뒤의 예시 결과입니다:
|
||||
|
||||

|
@@ -1,281 +0,0 @@
|
||||
# Bina Aplikasi Perbankan Bahagian 4: Konsep State Management
|
||||
|
||||
## Kuiz Pra Kuliah
|
||||
|
||||
[Kuiz Pra Kuliah](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/47)
|
||||
|
||||
### Pengenalan
|
||||
|
||||
Apabila aplikasi web berkembang, menjadi cabaran untuk mengawasi semua aliran data. Kod mana yang mendapat data, halaman mana yang menggunakannya, di mana dan kapan ia perlu dikemas kini ... mudah untuk berakhir dengan kod tidak kemas yang sukar dijaga. Ini benar terutamanya apabila anda perlu berkongsi data di antara halaman aplikasi anda yang berbeza, misalnya data pengguna. Konsep *state management* selalu ada dalam semua jenis program, tetapi ketika aplikasi web terus berkembang dalam kerumitan, kini menjadi titik penting untuk dipikirkan semasa pembangunan.
|
||||
|
||||
Pada bahagian akhir ini, kita akan melihat aplikasi yang kita buat untuk memikirkan kembali bagaimana keadaan dikendalikan, yang membolehkan sokongan penyegaran penyemak imbas pada bila-bila masa, dan data yang berterusan sepanjang sesi pengguna.
|
||||
|
||||
### Prasyarat
|
||||
|
||||
Anda perlu menyelesaikan bahagian [pengambilan data](../../3-data/translations/README.ms.md) pada aplikasi web untuk pelajaran ini. Anda juga perlu memasang [Node.js](https://nodejs.org) dan [jalankan API pelayan](../../api/README.ms.md) secara tempatan supaya anda dapat menguruskan data akaun.
|
||||
|
||||
Anda boleh menguji bahawa pelayan berjalan dengan betul dengan menjalankan perintah ini di terminal:
|
||||
|
||||
```sh
|
||||
curl http://localhost:5000/api
|
||||
# -> should return "Bank API v1.0.0" as a result
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Fikirkan semula state management
|
||||
|
||||
Dalam [pelajaran sebelumnya](../../3-data/translations/README.md), kami memperkenalkan konsep dasar keadaan dalam aplikasi kami dengan pemboleh ubah `akaun` global yang mengandungi data bank untuk pengguna yang sedang log masuk. Walau bagaimanapun, pelaksanaan kami sekarang mempunyai beberapa kekurangan. Cuba muat semula halaman semasa anda berada di papan pemuka. Apa yang berlaku?
|
||||
|
||||
Terdapat 3 masalah dengan kod semasa:
|
||||
|
||||
- Keadaan tidak berterusan, kerana penyegaran penyemak imbas membawa anda kembali ke halaman log masuk.
|
||||
- Terdapat pelbagai fungsi yang mengubah keadaan. Apabila aplikasinya berkembang, ini akan menyukarkan untuk mengesan perubahan dan mudah untuk melupakan pembaharuan.
|
||||
- Keadaan tidak dibersihkan, apabila anda mengklik *Logout* data akaun masih ada walaupun anda berada di halaman log masuk.
|
||||
|
||||
Kami dapat mengemas kini kod kami untuk mengatasi masalah ini satu demi satu, tetapi ini akan membuat lebih banyak pendua kod dan menjadikan aplikasi lebih rumit dan sukar untuk dijaga. Atau kita boleh berhenti sebentar dan memikirkan semula strategi kita.
|
||||
|
||||
> Masalah apa yang sebenarnya ingin kita selesaikan di sini?
|
||||
|
||||
[State Management](https://en.wikipedia.org/wiki/State_management) adalah mengenai mencari pendekatan yang baik untuk menyelesaikan dua masalah tertentu:
|
||||
|
||||
- Bagaimana agar aliran data dalam aplikasi dapat difahami?
|
||||
- Bagaimana cara menjaga data keadaan selalu selari dengan antara muka pengguna (dan sebaliknya)?
|
||||
|
||||
Setelah menyelesaikan masalah ini, masalah lain yang mungkin anda miliki mungkin sudah selesai atau menjadi lebih mudah untuk diselesaikan. Terdapat banyak kemungkinan pendekatan untuk menyelesaikan masalah ini, tetapi kami akan menggunakan penyelesaian bersama yang terdiri daripada **memusatkan data dan cara mengubahnya**. Aliran data akan seperti ini:
|
||||
|
||||

|
||||
|
||||
> Kami tidak akan membahas di sini bahagian di mana data secara automatik mencetuskan kemas kini paparan, kerana ia berkaitan dengan konsep [Pemrograman Reaktif](https://en.wikipedia.org/wiki/Reactive_programming). Ini adalah subjek susulan yang baik jika anda ingin menyelam.
|
||||
|
||||
✅ Terdapat banyak perpustakaan di luar sana dengan pendekatan yang berbeza untuk pengurusan negeri, [Redux](https://redux.js.org) menjadi pilihan yang popular. Lihat konsep dan corak yang digunakan kerana ini sering kali merupakan kaedah yang baik untuk mengetahui potensi masalah yang mungkin anda hadapi dalam aplikasi web besar dan bagaimana ia dapat diselesaikan.
|
||||
|
||||
### Tugas
|
||||
|
||||
Kita akan mulakan dengan sedikit refactoring. Ganti pernyataan `akaun`:
|
||||
|
||||
```js
|
||||
let account = null;
|
||||
```
|
||||
|
||||
With:
|
||||
|
||||
```js
|
||||
let state = {
|
||||
account: null
|
||||
};
|
||||
```
|
||||
|
||||
Ideanya adalah untuk *memusatkan* semua data aplikasi kami dalam satu objek keadaan. Kami hanya mempunyai `akaun` untuk saat ini di negeri ini sehingga tidak banyak berubah, tetapi ini menciptakan jalan untuk evolusi.
|
||||
|
||||
Kita juga harus mengemas kini fungsi menggunakannya. Dalam fungsi `register()` dan `login()`, ganti `account = ...` dengan `state.account = ...`;
|
||||
|
||||
Di bahagian atas fungsi `updateDashboard()`, tambahkan baris ini:
|
||||
|
||||
```js
|
||||
const account = state.account;
|
||||
```
|
||||
|
||||
Pemfaktoran semula ini dengan sendirinya tidak membawa banyak peningkatan, tetapi ideanya adalah untuk meletakkan asas untuk perubahan selanjutnya.
|
||||
|
||||
## Jejak perubahan data
|
||||
|
||||
Sekarang kita telah meletakkan objek `state` untuk menyimpan data kita, langkah seterusnya adalah memusatkan kemas kini. Tujuannya adalah untuk menjadikannya lebih mudah untuk mengikuti setiap perubahan dan kapan ia berlaku.
|
||||
|
||||
Untuk mengelakkan berlakunya perubahan pada objek `state`, adalah praktik yang baik untuk mempertimbangkannya [*tidak berubah*](https://en.wikipedia.org/wiki/Immutable_object), yang bermaksud bahawa ia tidak dapat diubah sama sekali. Ini juga bermaksud bahawa anda harus membuat objek keadaan baru jika anda ingin mengubah apa-apa di dalamnya. Dengan melakukan ini, anda membina perlindungan mengenai [kesan sampingan](https://en.wikipedia.org/wiki/Side_effect_ (computer_science)), dan membuka kemungkinan untuk ciri baru dalam aplikasi anda seperti melaksanakan undo / redo, sambil mempermudah debug. Sebagai contoh, anda boleh mencatat setiap perubahan yang dibuat ke negeri dan menyimpan sejarah perubahan untuk memahami sumber pepijat.
|
||||
|
||||
Dalam JavaScript, anda boleh menggunakan [`Object.freeze()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) untuk membuat versi yang tidak berubah dari sebuah objek. Sekiranya anda cuba membuat perubahan pada objek yang tidak dapat diubah, pengecualian akan ditimbulkan.
|
||||
|
||||
✅ Adakah anda tahu perbezaan antara objek *cetek* dan *dalam* tidak berubah? Anda boleh membacanya [di sini](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze#What_is_shallow_freeze).
|
||||
|
||||
### Tugas
|
||||
|
||||
Mari buat fungsi `updateState()` baru:
|
||||
|
||||
```js
|
||||
function updateState(property, newData) {
|
||||
state = Object.freeze({
|
||||
...state,
|
||||
[property]: newData
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
Dalam fungsi ini, kami membuat objek keadaan baru dan menyalin data dari keadaan sebelumnya menggunakan operator [*spread (`...`) operator*](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/Spread_syntax#Spread_in_object_literals). Kemudian kami menimpa harta benda objek tertentu dengan data baru menggunakan [notasi kurungan](https://developer.mozilla.org/docs/Web/JavaScript/Guide/Working_with_Objects#Objects_and_properties) `[property]` untuk tugasan. Akhirnya, kita mengunci objek untuk mengelakkan pengubahsuaian menggunakan `Object.freeze()`. Kami hanya menyimpan harta `akaun` di negeri ini buat masa ini, tetapi dengan pendekatan ini anda dapat menambahkan seberapa banyak harta tanah yang anda perlukan di negeri ini.
|
||||
|
||||
Kami juga akan mengemas kini inisialisasi `state` untuk memastikan keadaan awal juga dibekukan:
|
||||
|
||||
```js
|
||||
let state = Object.freeze({
|
||||
account: null
|
||||
});
|
||||
```
|
||||
|
||||
After that, update the `register` function by replacing the `state.account = result;` assignment with:
|
||||
|
||||
```js
|
||||
updateState('account', result);
|
||||
```
|
||||
|
||||
Do the same with the `login` function, replacing `state.account = data;` with:
|
||||
|
||||
```js
|
||||
updateState('account', data);
|
||||
```
|
||||
|
||||
Kami sekarang akan mengambil kesempatan untuk memperbaiki masalah data akaun yang tidak dihapus ketika pengguna mengklik *Logout*.
|
||||
|
||||
Buat fungsi baru `logout()`:
|
||||
|
||||
```js
|
||||
function logout() {
|
||||
updateState('account', null);
|
||||
navigate('/login');
|
||||
}
|
||||
```
|
||||
|
||||
Di `updateDashboard()`, ganti pengalihan `return navigate('/login');` dengan `return logout()`;
|
||||
|
||||
Cuba daftarkan akaun baru, log keluar dan masuk sekali lagi untuk memeriksa bahawa semuanya masih berfungsi dengan betul.
|
||||
|
||||
> Petua: anda dapat melihat semua perubahan keadaan dengan menambahkan `console.log(state)` di bahagian bawah `updateState()` dan membuka konsol di alat pengembangan penyemak imbas anda.
|
||||
|
||||
## Kekalkan keadaan
|
||||
|
||||
Sebilangan besar aplikasi web perlu mengekalkan data agar dapat berfungsi dengan betul. Semua data kritikal biasanya disimpan di pangkalan data dan diakses melalui API pelayan, seperti data akaun pengguna dalam kes kami. Tetapi kadang-kadang, juga menarik untuk mengekalkan beberapa data pada aplikasi klien yang berjalan di penyemak imbas anda, untuk pengalaman pengguna yang lebih baik atau untuk meningkatkan prestasi pemuatan.
|
||||
|
||||
Apabila anda ingin menyimpan data dalam penyemak imbas anda, terdapat beberapa soalan penting yang harus anda tanyakan kepada diri sendiri:
|
||||
|
||||
- *Adakah data sensitif?* Anda harus mengelakkan menyimpan data sensitif pada pelanggan, seperti kata laluan pengguna.
|
||||
- *Berapa lama anda perlu menyimpan data ini?* Adakah anda merancang untuk mengakses data ini hanya untuk sesi semasa atau adakah anda ingin menyimpannya selamanya?
|
||||
|
||||
Terdapat banyak cara untuk menyimpan maklumat di dalam aplikasi web, bergantung pada apa yang ingin anda capai. Sebagai contoh, anda boleh menggunakan URL untuk menyimpan pertanyaan carian, dan menjadikannya boleh dibagikan antara pengguna. Anda juga boleh menggunakan [kuki HTTP](https://developer.mozilla.org/docs/Web/HTTP/Cookies) jika data perlu dikongsi dengan pelayan, seperti [pengesahan](https://en.wikipedia.org/wiki/Authentication) maklumat.
|
||||
|
||||
Pilihan lain adalah menggunakan salah satu dari banyak API penyemak imbas untuk menyimpan data. Dua daripadanya sangat menarik:
|
||||
|
||||
- [`localStorage`](https://developer.mozilla.org/docs/Web/API/Window/localStorage): a [Key/Value store](https://en.wikipedia.org/wiki/Key%E2%80%93value_database) yang memungkinkan untuk mengekalkan data khusus untuk laman web semasa di pelbagai sesi. Data yang disimpan di dalamnya tidak akan luput.
|
||||
- [`sessionStorage`](https://developer.mozilla.org/docs/Web/API/Window/sessionStorage): ini berfungsi sama seperti `localStorage` kecuali data yang disimpan di dalamnya dibersihkan semasa sesi berakhir (semasa penyemak imbas ditutup).
|
||||
|
||||
Perhatikan bahawa kedua-dua API ini hanya membenarkan menyimpan [string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String). Sekiranya anda ingin menyimpan objek yang kompleks, anda perlu membuat siri ke siri [JSON](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/JSON) menggunakan [`JSON.stringify()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify).
|
||||
|
||||
✅ Sekiranya anda ingin membuat aplikasi web yang tidak berfungsi dengan pelayan, anda juga boleh membuat pangkalan data pada klien menggunakan [`IndexedDB` API](https://developer.mozilla.org/docs/Web/API/IndexedDB_API). Yang ini dikhaskan untuk kes penggunaan lanjutan atau jika anda perlu menyimpan sejumlah besar data, kerana lebih kompleks untuk digunakan.
|
||||
|
||||
### Tugas
|
||||
|
||||
Kami mahu pengguna kami terus masuk sehingga mereka mengklik butang *Logout* secara eksplisit, jadi kami akan menggunakan `localStorage` untuk menyimpan data akaun. Pertama, mari tentukan kunci yang akan kami gunakan untuk menyimpan data kami.
|
||||
|
||||
```js
|
||||
const storageKey = 'savedAccount';
|
||||
```
|
||||
|
||||
Kemudian tambahkan baris ini pada akhir fungsi `updateState()`:
|
||||
|
||||
```js
|
||||
localStorage.setItem(storageKey, JSON.stringify(state.account));
|
||||
```
|
||||
|
||||
Dengan ini, data akaun pengguna akan dikekalkan dan sentiasa terkini semasa kami memusatkan semua kemas kini negeri sebelumnya. Di sinilah kita mula mendapat manfaat daripada semua reaktor kita sebelumnya 🙂.
|
||||
|
||||
Semasa data disimpan, kita juga harus menjaga memulihkannya ketika aplikasi dimuat. Oleh kerana kita akan mempunyai lebih banyak kod inisialisasi, mungkin ada baiknya membuat fungsi `init` baru, yang juga merangkumi kod sebelumnya di bahagian bawah `app.js`:
|
||||
|
||||
```js
|
||||
function init() {
|
||||
const savedAccount = localStorage.getItem(storageKey);
|
||||
if (savedAccount) {
|
||||
updateState('account', JSON.parse(savedAccount));
|
||||
}
|
||||
|
||||
// Kod permulaan kami sebelumnya
|
||||
window.onpopstate = () => updateRoute();
|
||||
updateRoute();
|
||||
}
|
||||
|
||||
init();
|
||||
```
|
||||
|
||||
Di sini kami mengambil data yang disimpan, dan jika ada, kami akan mengemas kini keadaan dengan sewajarnya. Penting untuk melakukan ini *sebelum* mengemas kini laluan, kerana mungkin ada kod yang bergantung pada keadaan semasa kemas kini halaman.
|
||||
|
||||
Kami juga dapat menjadikan halaman *Dashboard* sebagai halaman lalai aplikasi kami, karena kami sekarang masih menyimpan data akun. Sekiranya tidak ada data, dashboard akan mengalihkan ke halaman *Login*. Dalam `updateRoute()`, ganti fallback `return navigate ('/login');` dengan `return navigate('dashboard');`.
|
||||
|
||||
Sekarang log masuk dalam aplikasi dan cuba memuat semula halaman, anda harus terus berada di papan pemuka. Dengan kemas kini itu, kami telah menangani semua masalah awal kami ...
|
||||
|
||||
## Muat semula data
|
||||
|
||||
... Tetapi kita mungkin juga telah membuat yang baru. Alamak!
|
||||
|
||||
Pergi ke papan pemuka menggunakan akaun `test`, kemudian jalankan perintah ini di terminal untuk membuat transaksi baru:
|
||||
|
||||
```sh
|
||||
curl --request POST \
|
||||
--header "Content-Type: application/json" \
|
||||
--data "{ \"date\": \"2020-07-24\", \"object\": \"Bought book\", \"amount\": -20 }" \
|
||||
http://localhost:5000/api/accounts/test/transactions
|
||||
```
|
||||
|
||||
Cuba muat semula halaman papan pemuka anda di penyemak imbas sekarang. Apa yang berlaku? Adakah anda melihat transaksi baru?
|
||||
|
||||
Keadaan ini berterusan selama-lamanya berkat `localStorage`, tetapi itu juga bermaksud ia tidak akan dikemas kini sehingga anda log keluar dari aplikasi dan log masuk semula!
|
||||
|
||||
Salah satu strategi yang mungkin untuk diperbaiki adalah memuat semula data akaun setiap kali dashboard dimuat, untuk mengelakkan data terhenti.
|
||||
|
||||
### Tugas
|
||||
|
||||
Buat fungsi baru `updateAccountData`:
|
||||
|
||||
```js
|
||||
async function updateAccountData() {
|
||||
const account = state.account;
|
||||
if (!account) {
|
||||
return logout();
|
||||
}
|
||||
|
||||
const data = await getAccount(account.user);
|
||||
if (data.error) {
|
||||
return logout();
|
||||
}
|
||||
|
||||
updateState('account', data);
|
||||
}
|
||||
```
|
||||
|
||||
Kaedah ini memeriksa bahawa kita sedang log masuk dan memuat semula data akaun dari pelayan.
|
||||
|
||||
Buat fungsi lain bernama `refresh`:
|
||||
|
||||
```js
|
||||
async function refresh() {
|
||||
await updateAccountData();
|
||||
updateDashboard();
|
||||
}
|
||||
```
|
||||
|
||||
Yang ini mengemas kini data akaun, kemudian mengurus mengemas kini HTML halaman papan pemuka. Inilah yang perlu kita panggil semasa laluan papan pemuka dimuat. Kemas kini definisi laluan dengan:
|
||||
|
||||
```js
|
||||
const routes = {
|
||||
'/login': { templateId: 'login' },
|
||||
'/dashboard': { templateId: 'dashboard', init: refresh }
|
||||
};
|
||||
```
|
||||
|
||||
Cuba muatkan semula papan pemuka sekarang, ia akan memaparkan data akaun yang dikemas kini.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Cabaran
|
||||
|
||||
Setelah kita memuatkan semula data akaun setiap kali papan pemuka dimuat, adakah anda fikir kita masih perlu meneruskan *semua data* akaun?
|
||||
|
||||
Cuba bekerjasama untuk mengubah apa yang disimpan dan dimuat dari `localStorage` untuk hanya memasukkan perkara yang benar-benar diperlukan agar aplikasi berfungsi.
|
||||
|
||||
## Post-Lecture Quiz
|
||||
|
||||
[Post-Lecture Quiz](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/48)
|
||||
|
||||
## Tugasan
|
||||
|
||||
[Laksanakan dialog "Tambah transaksi"](assignment.ms.md)
|
||||
|
||||
Inilah hasil contoh setelah menyelesaikan tugasan:
|
||||
|
||||

|
@@ -1,284 +0,0 @@
|
||||
# 建立銀行網頁應用程式 Part 4: 狀態控管的概念
|
||||
|
||||
## 課前測驗
|
||||
|
||||
[課前測驗](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/47?loc=zh_tw)
|
||||
|
||||
### 大綱
|
||||
|
||||
隨著網頁應用越來越龐大,追蹤資料流的動向也是一種挑戰。程式取得了何種資料、網頁如何處理它、何時何處被更新上去……這些很容易地導致程式碼凌亂而難以維護。尤其是當你需要在不同頁面上做資料共享時,好比說使用者的資料。*狀態控管(state management)* 的觀念已經存在於所有程式中,我們也開始需要在開發複雜的網頁應用程式時,注意這個關鍵點。
|
||||
|
||||
在這個最終章內,我們會總覽整個程式並重新思考該如何管理程式狀態,讓瀏覽器能在任何時刻做重新整理,在不同的使用者階段維持資料的狀態。
|
||||
|
||||
### 開始之前
|
||||
|
||||
你需要先完成[取得資料](../../3-data/translations/README.zh-tw.md)的網頁開發章節。你還需要安裝 [Node.js](https://nodejs.org) 並於本地端[執行伺服器 API](../../api/translations/README.zh-tw.md)以管理使用者資料。
|
||||
|
||||
你可以測試伺服器是否運作正常,在終端機中輸入指令:
|
||||
|
||||
```sh
|
||||
curl http://localhost:5000/api
|
||||
# -> should return "Bank API v1.0.0" as a result
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 思考狀態控管
|
||||
|
||||
在[前一堂課](../../3-data/translations/README.zh-tw.md)中,我們介紹了應用程式基本的狀態,全域變數 `account` 提供登入帳戶的相關銀行資料。然而,現在的專案存在著一些瑕疵。試著在儀表板介面中重新整理。發生了什麼事?
|
||||
|
||||
目前我們的程式碼有三個問題:
|
||||
|
||||
- 網頁狀態並沒有被儲存,當瀏覽器重新整理時,會被導回登入頁面。
|
||||
- 有許多函式會修改網頁狀態。隨著應用程式變大,我們很難去追蹤之後的改變,時刻地去更新相關的網頁狀態。
|
||||
- 網頁狀態並不完整,當你*登出*帳戶時,帳戶資訊仍然顯示在登入頁面上。
|
||||
|
||||
我們是可以逐一的解決這些問題,但這樣會創造出許多獨立的程式碼,讓應用程式更複雜而難以去管理。或者是我們停下來思考一下我們的策略。
|
||||
|
||||
> 我們究竟要解決什麼問題?
|
||||
|
||||
[狀態控管(State management)](https://en.wikipedia.org/wiki/State_management)可以為兩項問題提供良好的解決方案:
|
||||
|
||||
- 如何讓應用程式中的資料流容易理解?
|
||||
- 如何讓網頁狀態一直與使用者介面,或是相關物件進行同步?
|
||||
|
||||
一旦你處理好這些問題,其他問題可以被簡化,甚至被一併解決。有許多可能的方法能解決這些問題,但我們使用一種常見的解法:**中心化資料與更新方式**。資料流會呈現下列模式:
|
||||
|
||||

|
||||
|
||||
> 我們不會處理如何讓資料同步觸發頁面的更新,這比較像是關於[回應式程式設計](https://zh.wikipedia.org/wiki/%E5%93%8D%E5%BA%94%E5%BC%8F%E7%BC%96%E7%A8%8B)的更進階知識。當你更深入網頁開發領域時,這是個很好的發展方向。
|
||||
|
||||
✅ 有許多函式庫提供狀態管理的方式,[Redux](https://redux.js.org) 就是常見的選擇。閱讀它的概念與運作模式,這是種有效的的學習方式,讓你在大型的網頁開發中預測潛在的風險,並預想解決方案。
|
||||
|
||||
### 課題
|
||||
|
||||
我們會先做一些程式重構。替換掉 `account` 的定義:
|
||||
|
||||
```js
|
||||
let account = null;
|
||||
```
|
||||
|
||||
變成:
|
||||
|
||||
```js
|
||||
let state = {
|
||||
account: null
|
||||
};
|
||||
```
|
||||
|
||||
這個構想是要*中心化*應用程式資料到一個狀態物件中。目前我們只有 `account` 在狀態中,但這能提供未來新增新功能的基礎。
|
||||
|
||||
我們還需要更新與它相關的函式。在函式 `register()` 和 `login()` ,將 `account = ...` 替換為 `state.account = ...`。
|
||||
|
||||
在函式 `updateDashboard()` 的上方,加入此行:
|
||||
|
||||
```js
|
||||
const account = state.account;
|
||||
```
|
||||
|
||||
這個重構並不會帶來任何提升,但這是之後改變上的基礎。
|
||||
This refactoring by itself did not bring much improvements, but the idea was to lay out the foundation for the next changes.
|
||||
|
||||
## 追蹤資料改變
|
||||
|
||||
現在我們有 `state` 物件儲存資料了,接下來要來中心化這些更新。目標是能輕易地追蹤任何被觸發的改變。
|
||||
|
||||
為了避免改動 `state` 物件,我們考慮使它[*不可變*](https://zh.wikipedia.org/wiki/%E4%B8%8D%E5%8F%AF%E8%AE%8A%E7%89%A9%E4%BB%B6),意味著它不能被做任何的修改。
|
||||
這也代表你必須建立新的狀態物件來替換它。藉由這個方式,你就有一套保護措施阻絕潛在非預期[風險](https://zh.wikipedia.org/wiki/%E5%89%AF%E4%BD%9C%E7%94%A8_(%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6)),也開創出應用程式內還原與重做的功能,讓程式偵錯更加的容易。舉例來說,你可以紀錄狀態的改變,儲存狀態的歷史紀錄來了解錯誤的來源。
|
||||
|
||||
在 JavaScript 中,你可以使用 [`Object.freeze()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) 來建立不可變物件。若你想在不可變物件上做更動,例外處理(exception)就會發生。
|
||||
|
||||
✅ 你知道*淺複製(shallow)*和*深複製(deep)*這兩種不可變物件的差別嗎?你可以從[這裡](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze#What_is_shallow_freeze)閱讀相關資訊。
|
||||
|
||||
### 課題
|
||||
|
||||
我們來建立新的函式 `updateState()`:
|
||||
|
||||
```js
|
||||
function updateState(property, newData) {
|
||||
state = Object.freeze({
|
||||
...state,
|
||||
[property]: newData
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
在這個函式中,我們會建立新的狀態物件,並利用[*展開運算子(`...`)(Spread Operator)*](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/Spread_syntax#Spread_in_object_literals)複製前一個資料狀態。接著,我們使用[括弧記法(Bracket Notation)](https://developer.mozilla.org/docs/Web/JavaScript/Guide/Working_with_Objects#Objects_and_properties) `[property]` 賦予並覆蓋特定的狀態物件。最後,我們為物件上鎖,`Object.freeze()` 避免任何的改動。目前我們只有 `account` 資料存在狀態中,利用此方法可以讓你新增任何你想要的資料。
|
||||
|
||||
我們會更新 `state` 初始化設定,確保初始狀態也被上鎖:
|
||||
|
||||
```js
|
||||
let state = Object.freeze({
|
||||
account: null
|
||||
});
|
||||
```
|
||||
|
||||
接著,更新函式 `register`,將 `state.account = result;` 替換為:
|
||||
|
||||
```js
|
||||
updateState('account', result);
|
||||
```
|
||||
|
||||
在函式 `login` 上做一樣的事,將 `state.account = data;` 替換為:
|
||||
|
||||
```js
|
||||
updateState('account', data);
|
||||
```
|
||||
|
||||
藉由這個機會,我們能解決帳戶資料在*登出*時,不會被清除的問題。
|
||||
|
||||
建立新的函式 `logout()`:
|
||||
|
||||
```js
|
||||
function logout() {
|
||||
updateState('account', null);
|
||||
navigate('/login');
|
||||
}
|
||||
```
|
||||
|
||||
在 `updateDashboard()` 中,替換重新導向 `return navigate('/login');` 為 `return logout()`。
|
||||
|
||||
試著註冊新的帳戶,登入登出以確保功能都運作正常。
|
||||
|
||||
> 提示:你可以觀察所有的狀態改變,在 `updateState()` 裡的最下方加入 `console.log(state)`,開啟瀏覽器開發工具,命令欄就會顯示狀態的紀錄。
|
||||
|
||||
## 紀錄狀態
|
||||
|
||||
多數的網頁應用程式需要儲存資料以確保運作正常。所有重要的資料都會存在資料庫中,並藉由伺服器 API 來存取,就像我們專案中的帳戶資料。但有時候,瀏覽器用戶端的應用程式也需要儲存一些資料,提供更好的使用者體驗與增進負載效能。
|
||||
|
||||
當你想在瀏覽器內儲存資料,你必須思考幾項重要的問題:
|
||||
|
||||
- *這項資料很危險嗎?* 你應該要避免在用戶端儲存敏感的資料,例如帳戶密碼。
|
||||
- *你需要儲存資料多久?* 你打算短時間內做存取,還是永久地保存?
|
||||
|
||||
網頁應用程式中有許多儲存資訊的方法,一切都取決於你想達成的目標。舉例來說,你可以利用網址來儲存搜尋資訊,讓使用者間能共享資訊。若資料需要與伺服器共享,好比說[認證](https://zh.wikipedia.org/wiki/%E8%BA%AB%E4%BB%BD%E9%AA%8C%E8%AF%81)資訊,你可以使用 [HTTP cookies](https://developer.mozilla.org/docs/Web/HTTP/Cookies)。
|
||||
|
||||
另一個選擇是使用其中一個廣大的瀏覽器 API 來儲存資料。下列這兩項就特別有趣:
|
||||
|
||||
- [`localStorage`](https://developer.mozilla.org/docs/Web/API/Window/localStorage):[Key/Value 儲存法](https://zh.wikipedia.org/wiki/%E9%94%AE-%E5%80%BC%E5%AD%98%E5%82%A8)可以保存不同時刻的網頁資料。這些資料不會有期限的限制。
|
||||
- [`sessionStorage`](https://developer.mozilla.org/docs/Web/API/Window/sessionStorage):它的運作模式與 `localStorage` 相同,只差在資料會在網頁段落結束時被清除,如瀏覽器關閉時。
|
||||
|
||||
紀錄一下這兩個 API 只能儲存[字串](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)格式。
|
||||
如果你想儲存更複雜的物件,你需要利用 [`JSON.stringify()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) 將資料整理成 [JSON](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/JSON) 格式。
|
||||
|
||||
✅ 如果你想要建立不仰賴伺服器的網頁應用程式,你有辦法在用戶端建立資料庫。[`IndexedDB` API](https://developer.mozilla.org/docs/Web/API/IndexedDB_API) 可以應用在更進階的案例上,儲存更大量的資料,當然使用上也相對複雜。
|
||||
|
||||
### 課題
|
||||
|
||||
我們想讓使用者在登出之前,保持登入狀態。所以我們使用 `localStorage` 來儲存帳戶資料。首先,定義一組 key 來紀錄我們的資料內容。
|
||||
|
||||
```js
|
||||
const storageKey = 'savedAccount';
|
||||
```
|
||||
|
||||
在函式 `updateState()` 末端加入此行:
|
||||
|
||||
```js
|
||||
localStorage.setItem(storageKey, JSON.stringify(state.account));
|
||||
```
|
||||
|
||||
藉由此方式,帳戶資料就能保存下來,並隨著之前中心化後的狀態而更新。我們開始從之前的重構獲取效益了 🙂。
|
||||
|
||||
當資料被儲存後,我們還需要在程式讀取時載入資料。在 `app.js` 下方編寫更多的初始化程式,建立新的函式 `init` 並收入之前的程式碼:
|
||||
|
||||
```js
|
||||
function init() {
|
||||
const savedAccount = localStorage.getItem(storageKey);
|
||||
if (savedAccount) {
|
||||
updateState('account', JSON.parse(savedAccount));
|
||||
}
|
||||
|
||||
// 之前的初始化程式
|
||||
window.onpopstate = () => updateRoute();
|
||||
updateRoute();
|
||||
}
|
||||
|
||||
init();
|
||||
```
|
||||
|
||||
我們在此接收了儲存資料,並同步地更新狀態資訊。這必須在更新路由*之前*完成,否則有些程式碼會在頁面更新時,依據狀態來決定其行為。
|
||||
|
||||
當儲存完帳戶資料後,我們也定義了*儀表板*頁面為我們的預設首頁。若程式沒有找到資料,儀表板頁面也能重新導向回*登入*頁面。在 `updateRoute()` 中,替換回傳值 `return navigate('/login');` 為 `return navigate('/dashboard');`。
|
||||
|
||||
登入應用程式並重新整理頁面。你應該能維持在儀表板那頁。這個改變也解決了我們最初面臨的問題......
|
||||
|
||||
## 重整資料
|
||||
|
||||
......但我們可能也產生了新問題。啊呀!
|
||||
|
||||
使用 `test` 帳戶進入儀表板頁面,在終端機內執行下列指令以建立新的交易項目:
|
||||
|
||||
```sh
|
||||
curl --request POST \
|
||||
--header "Content-Type: application/json" \
|
||||
--data "{ \"date\": \"2020-07-24\", \"object\": \"Bought book\", \"amount\": -20 }" \
|
||||
http://localhost:5000/api/accounts/test/transactions
|
||||
```
|
||||
|
||||
試著重新整理瀏覽器內儀表板頁面。發生了什麼事?你有看到新的交易項目嗎?
|
||||
|
||||
感謝 `localStorage` 的幫助,狀態成功的儲存下來,但也代表我們在登出登入之前,不能再改變它的內容了!
|
||||
|
||||
一個可能的修復策略是在儀表板載入時,重新載入帳戶資訊以避免資料不同步。
|
||||
|
||||
### 課題
|
||||
|
||||
建立新的函式 `updateAccountData`:
|
||||
|
||||
```js
|
||||
async function updateAccountData() {
|
||||
const account = state.account;
|
||||
if (!account) {
|
||||
return logout();
|
||||
}
|
||||
|
||||
const data = await getAccount(account.user);
|
||||
if (data.error) {
|
||||
return logout();
|
||||
}
|
||||
|
||||
updateState('account', data);
|
||||
}
|
||||
```
|
||||
|
||||
這個方法能檢查我們是否已經登入,重新從伺服器載入用戶資料。
|
||||
|
||||
建立另一個函式 `refresh`:
|
||||
|
||||
```js
|
||||
async function refresh() {
|
||||
await updateAccountData();
|
||||
updateDashboard();
|
||||
}
|
||||
```
|
||||
|
||||
這能更新帳戶資料,更新 HTML 中的儀表板頁面。這是在儀表板路由被載入時,我們所需要呼叫的函式。更新路由定義為:
|
||||
|
||||
```js
|
||||
const routes = {
|
||||
'/login': { templateId: 'login' },
|
||||
'/dashboard': { templateId: 'dashboard', init: refresh }
|
||||
};
|
||||
```
|
||||
|
||||
試著重新載入儀表板,它現在應該能顯示更新後的帳戶資料。
|
||||
|
||||
---
|
||||
|
||||
## 🚀 挑戰
|
||||
|
||||
每一次儀表板載入時,我們都會重新載入帳戶資料,你認為我們還需要在用戶端儲存*所有的帳戶*資料嗎?
|
||||
|
||||
試著改變 `localStorage` 內的儲存內容,只包含我們能運行程式的必要資料。
|
||||
|
||||
## 課後測驗
|
||||
|
||||
[課後測驗](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/48?loc=zh_tw)
|
||||
|
||||
## 作業
|
||||
|
||||
[編寫"加入交易明細"視窗](assignment.zh-tw.md)
|
||||
|
||||
這邊有完成之後的結果:
|
||||
|
||||

|
@@ -1,24 +0,0 @@
|
||||
# Implementar el diálogo "Agregar transacción"
|
||||
|
||||
## Instrucciones
|
||||
|
||||
A nuestra aplicación bancaria todavía le falta una característica importante: la posibilidad de ingresar nuevas transacciones.
|
||||
Utilizando todo lo que ha aprendido en las cuatro lecciones anteriores, implemente un cuadro de diálogo "Agregar transacción":
|
||||
|
||||
- Agregue un botón "Agregar transacción" en la página del panel
|
||||
- Cree una nueva página con una plantilla HTML o use JavaScript para mostrar / ocultar el cuadro de diálogo HTML sin salir de la página del tablero (puede usar [`hidden`](https://developer.mozilla.org/docs/Web/HTML/Global_attributes/hidden) para eso, o clases CSS)
|
||||
- Implementar un formulario HTML para recibir datos de entrada.
|
||||
- Cree datos JSON a partir de los datos del formulario y envíelos a la API
|
||||
- Actualiza la página del tablero con los nuevos datos.
|
||||
|
||||
Mire las [especificaciones de la API del servidor](../images/dialog.png) para ver a qué API debe llamar y cuál es el formato JSON esperado.
|
||||
|
||||
Aquí hay un ejemplo de resultado después de completar la tarea:
|
||||
|
||||
! [Captura de pantalla que muestra un ejemplo de diálogo "Agregar transacción"](../images/dialog.png)
|
||||
|
||||
## Rúbrica
|
||||
|
||||
| Criterios | Ejemplar | Adecuado | Necesita mejorar |
|
||||
| -------- | -------------------------------------------------- ---------------------------------------------- | -------------------------------------------------- -------------------------------------------------- ------------------- | -------------------------------------------- |
|
||||
| | La adición de una transacción se implementa completamente siguiendo todas las mejores prácticas que se ven en las lecciones. | Agregar una transacción está implementado, pero no siguiendo las mejores prácticas que se ven en las lecciones, o trabajando solo parcialmente. | Agregar una transacción no funciona en absoluto. |
|
@@ -1,25 +0,0 @@
|
||||
# Implémenter la boîte de dialogue "Ajouter une transaction"
|
||||
|
||||
## Instructions
|
||||
|
||||
Il manque encore à notre application bancaire une fonctionnalité importante: la possibilité de saisir de nouvelles transactions.
|
||||
En utilisant tout ce que vous avez appris dans les quatre leçons précédentes, implémentez une boîte de dialogue « Ajouter une transaction » :
|
||||
|
||||
- Ajouter un bouton "Ajouter une transaction" dans la page du tableau de bord
|
||||
- Créez une nouvelle page avec un modèle HTML ou utilisez JavaScript pour afficher/masquer le HTML de la boîte de dialogue sans quitter la page du tableau de bord (vous pouvez utiliser [`hidden`](https://developer.mozilla.org/docs/Web/HTML/Global_attributes/hidden) pour celà, ou des classes CSS)
|
||||
- Assurez-vous de gérer [l'accessibilité du clavier et du lecteur d'écran](https://developer.paciellogroup.com/blog/2018/06/the-current-state-of-modal-dialog-accessibility/) pour la boîte de dialogue
|
||||
- Implémenter un formulaire HTML pour recevoir les données d'entrée
|
||||
- Créer des données JSON à partir des données du formulaire et les envoyer à l'API
|
||||
- Mettre à jour la page du tableau de bord avec les nouvelles données
|
||||
|
||||
Consultez les [spécifications de l'API du serveur](./../../api/translations/README.fr.md) pour voir quelle API vous devez appeler et quel est le format JSON attendu.
|
||||
|
||||
Voici un exemple de résultat après avoir terminé le devoir :
|
||||
|
||||

|
||||
|
||||
## Rubrique
|
||||
|
||||
| Critères | Exemplaire | Adéquat | Besoin d'amélioration |
|
||||
| -------- | ------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- | --------------------------------------------|
|
||||
| | L'ajout d'une transaction est entièrement mis en œuvre en suivant toutes les meilleures pratiques vues dans les leçons. | L'ajout d'une transaction est implémenté, mais ne suit pas les meilleures pratiques vues dans les leçons, ou ne fonctionne que partiellement. | L'ajout d'une transaction ne fonctionne pas du tout. |
|
@@ -1,25 +0,0 @@
|
||||
# लेनदेन जोड़ें डियलॉग लागू करें
|
||||
|
||||
## अनुदेश
|
||||
|
||||
हमारा बैंक ऐप अभी भी एक महत्वपूर्ण विशेषता को याद कर रहा है: नए लेनदेन दर्ज करने की संभावना।
|
||||
पिछले चार पाठों में आपने जो कुछ भी सीखा है, उसका उपयोग करके "लेनदेन जोड़ें" डियलॉग को लागू करें:
|
||||
|
||||
- डैशबोर्ड पृष्ठ में "लेनदेन जोड़ें" बटन जोड़ें
|
||||
- या तो HTML टेम्पलेट के साथ एक नया पृष्ठ बनाएँ, या डैशबोर्ड पृष्ठ को छोड़े बिना संवाद HTML दिखाने / छिपाने के लिए जावास्क्रिप्ट का उपयोग करें (आप उसके लिए, या CSS कक्षाओं के लिए [`hidden`](https://developer.mozilla.org/docs/Web/HTML/Global_attributes/hidden) हुई संपत्ति का उपयोग कर सकते हैं)
|
||||
- सुनिश्चित करें कि आप [कीबोर्ड और स्क्रीन रीडर एक्सेसिबिलिटी](https://developer.paciellogroup.com/blog/2018/06/the-current-state-of-modal-dialog-accessibility/) डियलॉग संभालते हैं
|
||||
- इनपुट डेटा प्राप्त करने के लिए एक HTML फॉर्म को लागू करें
|
||||
- फॉर्म डेटा से JSON डेटा बनाएं और इसे API पर भेजें
|
||||
- नए डेटा के साथ डैशबोर्ड पृष्ठ को अपडेट करें
|
||||
|
||||
[सर्वर एपीआई विनिर्देशों](../../api/README.md) को देखें कि आपको कौन सा एपीआई कॉल करने की आवश्यकता है और जो JSON प्रारूप अपेक्षित है उसे देखने के लिए।
|
||||
|
||||
यहां असाइनमेंट पूरा करने के बाद एक उदाहरण परिणाम है:
|
||||
|
||||

|
||||
|
||||
## शीर्ष
|
||||
|
||||
| मानदंड | उदाहरणात्मक | पर्याप्त | सुधार की जरूरत |
|
||||
| ------ | -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
|
||||
| | लेन-देन को जोड़ना पाठों में देखी जाने वाली सभी सर्वोत्तम प्रथाओं का पूरी तरह से पालन किया जाता है. | लेन-देन जोड़ना कार्यान्वयन है, लेकिन पाठों में देखी गई सर्वोत्तम प्रथाओं का पालन नहीं करना, या केवल आंशिक रूप से काम करना. | लेनदेन जोड़ना बिल्कुल भी काम नहीं कर रहा है. |
|
@@ -1,25 +0,0 @@
|
||||
# Implementare la finestra di dialogo "Aggiungi transazione"
|
||||
|
||||
## Istruzioni
|
||||
|
||||
All'app bancaria manca ancora una caratteristica importante: la possibilità di inserire nuove transazioni.
|
||||
Utilizzando tutto quanto appreso nelle quattro lezioni precedenti, implementare una finestra di dialogo "Aggiungi transazione":
|
||||
|
||||
- Aggiungere un pulsante "Aggiungi transazione" nella pagina del cruscotto
|
||||
- Creare una nuova pagina con un modello HTML o usare JavaScript per mostrare/nascondere l'HTML della finestra di dialogo senza lasciare la pagina del cruscotto (si può usare la proprietà [`hidden`](https://developer.mozilla.org/docs/Web/HTML/Global_attributes/hidden) per quello o le classi CSS)
|
||||
- Assicurarsi di gestire l' [accessibilità dalla tastiera e dal lettore di schermo](https://developer.paciellogroup.com/blog/2018/06/the-current-state-of-modal-dialog-accessibility/) per la finestra di dialogo
|
||||
- Implementare un form HTML per ricevere i dati di input
|
||||
- Creare dati JSON dai dati del form e inviarli all'API
|
||||
- Aggiorna la pagina del cruscotto con i nuovi dati
|
||||
|
||||
Guardare [le specifiche dell'API del server](../api/README.md) per vedere quale API si devono chiamare e qual è il formato JSON previsto.
|
||||
|
||||
Ecco un esempio di risultato dopo aver completato il compito:
|
||||
|
||||

|
||||
|
||||
## Rubrica
|
||||
|
||||
| Criteri | Ottimo | Adeguato | Necessita miglioramento |
|
||||
| -------- | ------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- | --------------------------------------------|
|
||||
| | L'aggiunta di una transazione viene implementata seguendo completamente tutte le migliori pratiche viste nelle lezioni. | L'aggiunta di una transazione è implementata, ma non seguendo le migliori pratiche viste nelle lezioni o funzionante solo parzialmente. | L'aggiunta di una transazione non funziona affatto. |
|
@@ -1,25 +0,0 @@
|
||||
# 「トランザクションの追加」ダイアログの実装
|
||||
|
||||
## 説明書
|
||||
|
||||
私たちの銀行アプリには、1つの重要な機能がまだありません。
|
||||
前の4つのレッスンで学んだことをすべて使って、「トランザクションの追加」ダイアログを実装します。
|
||||
|
||||
- ダッシュボードページに「トランザクションの追加」ボタンを追加します
|
||||
- HTML テンプレートで新しいページを作成するか、JavaScript を使用してダッシュボード・ページを離れることなくダイアログの HTML を表示/非表示にするかのいずれかを選択します (そのためには [`hidden`](https://developer.mozilla.org/ja/docs/Web/HTML/Global_attributes/hidden) プロパティを使用するか、CSS クラスを使用することができます)
|
||||
- ダイアログの [キーボードとスクリーンリーダーのアクセシビリティ] (https://developer.paciellogroup.com/blog/2018/06/the-current-state-of-modal-dialog-accessibility/) が処理されていることを確認します
|
||||
- 入力データを受け取るための HTML フォームを実装する
|
||||
- フォームデータから JSON データを作成して API に送る
|
||||
- ダッシュボードページを新しいデータで更新する
|
||||
|
||||
[サーバー API の仕様](./.../.../api/translations/README.ja.md)を見て、どの API を呼び出す必要があるか、期待される JSON 形式は何かを確認してください。
|
||||
|
||||
以下は、課題を完了した後の結果の例です。
|
||||
|
||||

|
||||
|
||||
## ルーブリック
|
||||
|
||||
| 基準 | 模範的な例 | 適切な | 改善が必要 |
|
||||
| -------- | ------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- | --------------------------------------------|
|
||||
| | トランザクションの追加は、レッスンで見たすべてのベストプラクティスに従って完全に実装されています。 | トランザクションを追加することは実装されていますが、レッスンで見たベストプラクティスに従っていなかったり、部分的にしか動作しなかったりします。 | トランザクションを追加しても全く機能しません。 |
|
@@ -1,24 +0,0 @@
|
||||
# "거래 추가" 대화 상자 구현
|
||||
|
||||
## 설명
|
||||
|
||||
우리 은행 앱에는 여전히 새로운 거래를 입력 할 수 있는 한 가지 중요한 기능이 없습니다. 이전 네 가지 수업에서 배운 모든 것을 사용하여 "트랜잭션 추가" 대화 상자를 구현해봅시다.
|
||||
|
||||
- 대시 보드 페이지에 "거래 추가" 버튼 추가
|
||||
- HTML 템플릿으로 새 페이지를 만들거나 JavaScript를 사용하여 대시 보드 페이지를 벗어나지 않고 대화 상자 HTML을 표시하거나 숨깁니다(해당 항목에 대해 [`hidden`](https://developer.mozilla.org/docs/Web/HTML/Global_attributes/hidden) 속성 또는 CSS 클래스를 사용할 수 있음).
|
||||
- 대화 상자에 대한 [키보드 및 화면 판독기 접근성](https://developer.paciellogroup.com/blog/2018/06/the-current-state-of-modal-dialog-accessibility/)을 처리해야합니다.
|
||||
- 입력 데이터 수신을 위한 HTML 양식 구현
|
||||
- 양식 데이터에서 JSON 데이터를 만들어 API로 보냅니다.
|
||||
- 새 데이터로 대시 보드 페이지 업데이트
|
||||
|
||||
호출해야하는 API와 예상되는 JSON 형식을 확인하려면 [서버 API 사양](../api/README.md) 을 살펴보세요.
|
||||
|
||||
다음은 과제를 완료 한 후의 예시 결과입니다.
|
||||
|
||||

|
||||
|
||||
## 평가 기준
|
||||
|
||||
기준 | 모범 답안 | 적당한 답안 | 개선이 필요한 답안
|
||||
--- | --- | --- | ---
|
||||
| 거래 추가 기능이 수업에서 본 모든 모범 사례에 따라 완전히 구현된 경우 | 거래 추가기능은 구현되지만, 강의에 표시된 모범 사례를 따르지 않거나 부분적으로만 작동하는 경우 | 거래 추가기능이 전혀 동작하지 않는 경우
|
@@ -1,25 +0,0 @@
|
||||
# Laksanakan dialog "Tambah transaksi"
|
||||
|
||||
## Arahan
|
||||
|
||||
Aplikasi bank kami masih belum mempunyai satu ciri penting: kemungkinan untuk melakukan transaksi baru.
|
||||
Menggunakan semua yang telah anda pelajari dalam empat pelajaran sebelumnya, laksanakan dialog "Tambah transaksi":
|
||||
|
||||
- Tambahkan butang "Tambah transaksi" di halaman papan pemuka
|
||||
- Buat halaman baru dengan templat HTML, atau gunakan JavaScript untuk menunjukkan / menyembunyikan HTML dialog tanpa meninggalkan halaman papan pemuka (anda boleh menggunakan [`hidden`](https://developer.mozilla.org/docs/Web/HTML/Global_attributes/hidden) property for that, or CSS classes) untuk itu, atau kelas CSS)
|
||||
- Pastikan anda mengendalikan [kebolehcapaian pembaca papan kekunci dan skrin](https://developer.paciellogroup.com/blog/2018/06/the-current-state-of-modal-dialog-accessibility/) untuk dialog
|
||||
- Terapkan borang HTML untuk menerima data input
|
||||
- Buat data JSON dari data formulir dan kirimkan ke API
|
||||
- Kemas kini halaman papan pemuka dengan data baru
|
||||
|
||||
Lihat [server API specifications](../../api/README.ms.md) untuk melihat API mana yang perlu anda panggil dan format JSON yang diharapkan.
|
||||
|
||||
Inilah hasil contoh setelah menyelesaikan tugasan:
|
||||
|
||||

|
||||
|
||||
## Rubrik
|
||||
|
||||
| Kriteria | Contoh | Mencukupi | Usaha Lagi |
|
||||
| -------- | ------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- | --------------------------------------------|
|
||||
| | Menambah transaksi dilaksanakan sepenuhnya mengikuti semua amalan terbaik yang dilihat dalam pelajaran. | Menambah urus niaga dilaksanakan, tetapi tidak mengikuti amalan terbaik yang dapat dilihat dalam pelajaran, atau hanya bekerja sebahagian. | Menambah urus niaga tidak berfungsi sama sekali. |
|
@@ -1,25 +0,0 @@
|
||||
# Implementeer het dialoogvenster "Transactie toevoegen"
|
||||
|
||||
## Instructies
|
||||
|
||||
Onze bank-app mist nog één belangrijk kenmerk: de mogelijkheid om nieuwe transacties in te voeren.
|
||||
Gebruik alles wat u in de vier voorgaande lessen heeft geleerd en implementeer een dialoogvenster "Transactie toevoegen":
|
||||
|
||||
- Voeg een knop "Transactie toevoegen" toe op de dashboardpagina
|
||||
- Maak een nieuwe pagina met een HTML-sjabloon of gebruik JavaScript om de HTML-dialoog weer te geven/te verbergen zonder de dashboardpagina te verlaten (u kunt [`hidden`](https://developer.mozilla.org/docs/Web/HTML/Global_attributes/hidden) eigenschap daarvoor gebruiken, of CSS-class)
|
||||
- Zorg ervoor dat u [toetsenbord en schermlezer toegankelijkheid](https://developer.paciellogroup.com/blog/2018/06/the-current-state-of-modal-dialog-accessibility/) afhandelt voor het dialoogvenster
|
||||
- Implementeer een HTML-formulier om invoergegevens te ontvangen
|
||||
- Maak JSON-gegevens van de formuliergegevens en stuur deze naar de API
|
||||
- Werk de dashboardpagina bij met de nieuwe gegevens
|
||||
|
||||
Kijk naar de [server API-specificaties](../api/README.md) om te zien welke API u moet aanroepen en wat het verwachte JSON-formaat is.
|
||||
|
||||
Hier is een voorbeeldresultaat na het voltooien van de opdracht:
|
||||
|
||||

|
||||
|
||||
## Rubriek
|
||||
|
||||
| Criteria | Voorbeeldig | Voldoende | Moet worden verbeterd |
|
||||
| -------- | ------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- | --------------------------------------------|
|
||||
| | Het toevoegen van een transactie wordt volledig geïmplementeerd volgens alle best practices uit de lessen. | Het toevoegen van een transactie is geïmplementeerd, maar volgt niet de best practices uit de lessen, of werkt slechts gedeeltelijk. | Het toevoegen van een transactie werkt helemaal niet. |
|
@@ -1,25 +0,0 @@
|
||||
# 編寫"加入交易明細"視窗
|
||||
|
||||
## 簡介
|
||||
|
||||
我們的銀行應用程式還缺乏一項重要的功能:輸入新的交易明細。
|
||||
使用你在這四堂課中學到的知識,編寫"加入交易明細"視窗:
|
||||
|
||||
- 在儀表板頁面新增"加入交易明細"按鈕
|
||||
- 加入新的 HTML 模板建立新頁面,或是在同一頁面中使用 JavaScript 顯示 HTML 窗格(可以使用 [`hidden`](https://developer.mozilla.org/docs/Web/HTML/Global_attributes/hidden) 屬性,或是 CSS classes)
|
||||
- 確保視窗能滿足[鍵盤與螢幕報讀器的相容性](https://developer.paciellogroup.com/blog/2018/06/the-current-state-of-modal-dialog-accessibility/)
|
||||
- 編寫 HTML 表單來接收輸入資料
|
||||
- 建立 JSON 表單資料並傳送到 API 上
|
||||
- 使用新資料更新到儀表板頁面上
|
||||
|
||||
看看[伺服器 API 規格](../../api/README.zh-tw.md)來查詢你需要呼叫的 API 和所需的 JSON 格式。
|
||||
|
||||
這邊有完成作業後的成果:
|
||||
|
||||

|
||||
|
||||
## 學習評量
|
||||
|
||||
| 作業內容 | 優良 | 普通 | 待改進 |
|
||||
| -------- | ------------------------------------ | -------------------------------------------------- | ---------------------- |
|
||||
| | 利用課程內容完美的製作出交易明細功能 | 有製作出交易明細功能,但有缺少部分要點且功能不完全 | 新的交易明細功能不正常 |
|
@@ -1,13 +0,0 @@
|
||||
# Aplicación bancaria
|
||||
|
||||
> Solución de ejemplo para el proyecto de la aplicación bancaria, construida con HTML5 vanilla, CSS y JavaScript (sin entorno de trabajos 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.
|
@@ -1,13 +0,0 @@
|
||||
# Application bancaire
|
||||
|
||||
> Exemple de solution pour le projet d'application bancaire, développé avec du HTML5 vanilla, du CSS et du JavaScript (pas de frameworks ni de bibliothèques utilisés).
|
||||
|
||||
## Exécuter l'application
|
||||
|
||||
Assurez-vous d'abord que le [serveur API](../../api/translations/README.fr.md) est en cours d'exécution.
|
||||
|
||||
N'importe quel serveur Web peut être utilisé pour exécuter l'application, mais comme [Node.js](https://nodejs.org/fr) doit de toute façon être installé pour exécuter l'API, vous pouvez :
|
||||
|
||||
1. Git cloner ce dépôt.
|
||||
2. Ouvrir un terminal, puis exécutez `npx lite-server solution`. Celà démarrera un serveur Web de développement sur le port `3000`
|
||||
3. Ouvrir `http://localhost:3000` dans un navigateur pour exécuter l'application.
|
@@ -1,13 +0,0 @@
|
||||
# बैंक एप
|
||||
|
||||
> बैंक ऐप परियोजना के लिए उदाहरण समाधान, वेनिला एचटीएमएल 5, सीएसएस और जावास्क्रिप्ट के साथ बनाया गया (कोई फ्रेमवर्क या लाइब्रेरी का उपयोग नहीं किया गया) है.
|
||||
|
||||
## ऐप चल रहा हैं
|
||||
|
||||
First make sure you have the [API server](../api/README.md) running पहले सुनिश्चित करें कि आपके पास [एपीआई सर्वर](../../api/translations/README.hi.md) चल रहा है.
|
||||
|
||||
एप्लिकेशन को चलाने के लिए किसी भी वेब सर्वर का उपयोग किया जा सकता है, लेकिन चूंकि आपके पास एपीआई चलाने के लिए वैसे भी [नोडजेयस](https://nodejs.org) स्थापित होना चाहिए, आप कर सकते हैं:
|
||||
|
||||
1. ये रेपो गिट क्लोन.
|
||||
2. एक टर्मिनल खोलें, फिर `npx lite-server solution` चलाएँ। यह पोर्ट `3000` पर एक विकास वेब सर्वर शुरू करेगा
|
||||
3. एप्लिकेशन चलाने के लिए ब्राउज़र में `http://localhost:3000` खोलें.
|
@@ -1,13 +0,0 @@
|
||||
# App Bancaria
|
||||
|
||||
> Soluzione di esempio per il progetto app bancaria, costruito con semplice HTML5, CSS e JavaScript (non è stata usato alcun framework o libreria).
|
||||
|
||||
## Eseguire l'app
|
||||
|
||||
Per prima cosa assicurarsi di avere in esecuzione il [server API](../../api/translations/README.it.md).
|
||||
|
||||
Può essere usato un qualsiasi server web per eseguire l'app, ma visto che si dovrebbe avere installato comunque [Node.js](https://nodejs.org) per eseguire l'API, è possibile:
|
||||
|
||||
1. Utilizzare il comando `git clone` con questo repository.
|
||||
2. Aprire un terminale, poi eseguire `npx lite-server solution`. Verrà fatto partire un server web di sviluppo sulla porta `3000`
|
||||
3. Aprire `http://localhost:3000` in un browser per eseguire l'app.
|
@@ -1,13 +0,0 @@
|
||||
# 銀行アプリ
|
||||
|
||||
> バニラ HTML5、CSS、JavaScript で構築された銀行アプリプロジェクトのソリューション例 (フレームワークやライブラリは使用していません)。
|
||||
|
||||
## アプリの実行
|
||||
|
||||
まず、[API サーバー](../../api/translations/README.ja.md)が起動していることを確認します。
|
||||
|
||||
アプリの実行にはどの Web サーバーを使用しても構いませんが、API を実行するためには [Node.js](https://nodejs.org/ja) がインストールされている必要があるので、以下のようにします。
|
||||
|
||||
1. このレポを Git でクローンします
|
||||
2. ターミナルを開き、`npx lite-server solution` を実行します。これで、`3000` ポートで開発用ウェブサーバが起動します
|
||||
3. ブラウザで `http://localhost:3000` を開いてアプリを実行します
|
@@ -1,15 +0,0 @@
|
||||
# 은행 앱
|
||||
|
||||
> 은행 앱 프로젝트의 예시 답안입니다. 바닐라 HTML5, CSS 그리고 JavaScript를 이용해 구현하였습니다(어떠한 프레임워크나 라이브러리도 사용되지 않았습니다).
|
||||
|
||||
## 앱을 실행하기
|
||||
|
||||
먼저 [API server](../api/README.md)가 동작하고 있는지 확인해주세요.
|
||||
|
||||
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:
|
||||
|
||||
어느 웹서버나 이 앱을 사용할 수 있으나, 아직까지는 이 API를 실행하기 위해서는 [Node.js](https://nodejs.org)를 설치해야 합니다. 아래의 방법으로 실행하세요:
|
||||
|
||||
1. 이 저장소를 Git clone 합니다.
|
||||
2. 터미널을 열고, `npx lite-server solution`을 입력해 실행합니다. 이 명령은 `3000`번 포트에 개발 웹 서버를 시작하게 합니다.
|
||||
3. 브라우저에서 `http://localhost:3000` 입력해 앱을 실행합니다.
|
@@ -1,13 +0,0 @@
|
||||
# ബാങ്ക് ആപ്പ്
|
||||
|
||||
> വാനില HTML5, CSS, JavaScript എന്നിവ ഉപയോഗിച്ച് നിർമ്മിച്ച ബാങ്ക് ആപ്പ് പ്രോജക്റ്റിനുള്ള ഉദാഹരണ പരിഹാരം (ചട്ടക്കൂടുകളോ ലൈബ്രറികളോ ഉപയോഗിച്ചിട്ടില്ല).
|
||||
|
||||
## ആപ്പ് പ്രവർത്തിപ്പിക്കുന്നു
|
||||
|
||||
ആദ്യം നിങ്ങൾക്ക് [API സെർവർ](../api/README.md) പ്രവർത്തിക്കുന്നുണ്ടെന്ന് ഉറപ്പാക്കുക.
|
||||
|
||||
ആപ്പ് പ്രവർത്തിപ്പിക്കാൻ ഏത് വെബ് സെർവറും ഉപയോഗിക്കാം, എന്നാൽ API പ്രവർത്തിപ്പിക്കുന്നതിന് നിങ്ങൾക്ക് [Node.js](https://nodejs.org) ഇൻസ്റ്റാൾ ചെയ്തിരിക്കേണ്ടതിനാൽ, നിങ്ങൾക്ക് ഇവ ചെയ്യാനാകും:
|
||||
|
||||
1. ഈ റിപ്പോ Git ക്ലോൺ ചെയ്യുക.
|
||||
2. ഒരു ടെർമിനൽ തുറക്കുക, തുടർന്ന് `npx lite-server solution` റൺ ചെയ്യുക. അത് `3000` പോർട്ടിൽ ഒരു വികസന വെബ് സെർവർ ആരംഭിക്കും
|
||||
3. ആപ്പ് പ്രവർത്തിപ്പിക്കുന്നതിന് ഒരു ബ്രൗസറിൽ `http://localhost:3000` തുറക്കുക.
|
@@ -1,13 +0,0 @@
|
||||
# Aplikasi bank
|
||||
|
||||
> Contoh penyelesaian untuk projek aplikasi bank, dibangun dengan vanilla HTML5, CSS dan JavaScript (tidak ada kerangka atau perpustakaan yang digunakan).
|
||||
|
||||
## Menjalankan aplikasi
|
||||
|
||||
Mula-mula pastikan anda menjalankan [pelayan API](../../api/translations/README.ms.md).
|
||||
|
||||
Mana-mana pelayan web boleh digunakan untuk menjalankan aplikasi, tetapi kerana anda seharusnya memasang [Node.js](https://nodejs.org) untuk menjalankan API, anda boleh:
|
||||
|
||||
1. Git klon repo ini.
|
||||
2. Buka terminal, kemudian jalankan `npx lite-server solution`. Ia akan memulakan pelayan web pengembangan di port `3000`
|
||||
3. Buka `http://localhost: 3000` di penyemak imbas untuk menjalankan aplikasi.
|
@@ -1,17 +0,0 @@
|
||||
# :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.
|
||||
|
||||
|  |  |
|
||||
|-----------------------------------|-----------------------------------|
|
||||
|
||||
## Lecciones
|
||||
|
||||
1. [Rutas y plantillas HTML en una aplicación web](../1-template-route/translations/README.es.md)
|
||||
2. [Cree un formulario de inicio de sesión y registro](../2-forms/translations/README.es.md)
|
||||
3. [Métodos de obtención y uso de datos](../3-data/translations/README.es.md)
|
||||
4. [Conceptos de gestión de estado](../4-state-management/translations/README.es.md)
|
||||
|
||||
### Credits
|
||||
|
||||
Estas lecciones fueron escritas con :hearts: por [Yohan Lasorsa](https://twitter.com/sinedied)
|
@@ -1,21 +0,0 @@
|
||||
# :dollar: Créer une banque
|
||||
|
||||
Dans ce projet, vous apprendrez à créer une banque fictive. Ces leçons comprennent des instructions sur la façon de mettre en page une application Web et de fournir des routes, de construire des formulaires, de gérer l'état et de récupérer les données d'une API à partir de laquelle vous pouvez récupérer les données de la banque.
|
||||
|
||||
|  |  |
|
||||
|--------------------------------|--------------------------------|
|
||||
|
||||
## Leçons
|
||||
|
||||
1. [Modèles HTML et routages dans une application Web](../1-template-route/translations/README.fr.md)
|
||||
2. [Créer un formulaire de connexion et d'inscription](../2-forms/translations/README.fr.md)
|
||||
3. [Méthodes d'extraction et d'utilisation des données](../3-data/translations/README.fr.md)
|
||||
4. [Concepts de gestion de l'État](../4-state-management/translations/README.fr.md)
|
||||
|
||||
### Crédits
|
||||
|
||||
Ces leçons ont été rédigées avec :hearts: par [Yohan Lasorsa](https://twitter.com/sinedied).
|
||||
|
||||
Si vous souhaitez apprendre comment créer l'[API serveur](../api/translations/README.fr.md) utilisée dans ces leçons, vous pouvez suivre [cette série de videos](https://aka.ms/NodeBeginner) (en particulier les vidéos 17 à 21).
|
||||
|
||||
Vous pouvez également jeter un coup d'œil à [ce tutoriel d'apprentissage interactif](https://aka.ms/learn/express-api).
|
@@ -1,21 +0,0 @@
|
||||
# : डॉलर: एक बैंक बनाएँ
|
||||
|
||||
इस परियोजना में, आप सीखेंगे कि एक काल्पनिक बैंक कैसे बनाया जाए। इन पाठों में एक वेब ऐप को लेआउट करने और मार्ग प्रदान करने, प्रपत्र बनाने, राज्य का प्रबंधन करने और एपीआई से डेटा प्राप्त करने के निर्देश शामिल हैं, जिनसे आप बैंक का डेटा प्राप्त कर सकते हैं।.
|
||||
|
||||
|  |  |
|
||||
| ----------------------------------- | ----------------------------------- |
|
||||
|
||||
## पाठ
|
||||
|
||||
1. [HTML टेम्पलेट्स और वेब ऐप में रूट](../1-template-route/README.hi.md)
|
||||
2. [एक लॉगिन और पंजीकरण फॉर्म बनाएँ](../2-forms/README.hi.md)
|
||||
3. [डेटा लाने और उपयोग करने के तरीके](../3-data/README.hi.md)
|
||||
4. [राज्य प्रबंधन की अवधारणाएँ](../4-state-management/README.hi.md)
|
||||
|
||||
### आभार सूची
|
||||
|
||||
[Yohan Lasorsa](https://twitter.com/sinedied) द्वारा इन पाठों को :hearts: के साथ लिखा गया था.
|
||||
|
||||
यदि आप सीखना चाहते हैं कि इन पाठों में उपयोग किए जाने वाले [सर्वर एपीआई](../api/translations/README.hi.md) का निर्माण कैसे किया जाए, तो आप [वीडियो की यह श्रृंखला](https://aka.ms/NodeBeginner) का अनुसरण कर सकते हैं (विशेष रूप से वीडियो १७ से २१).
|
||||
|
||||
आप [इस इंटरएक्टिव लर्न ट्यूटोरियल](https://aka.ms/learn/express-api) पर भी नज़र डाल सकते हैं.
|
@@ -1,21 +0,0 @@
|
||||
# :dollar: Costruire una Banca
|
||||
|
||||
In questo progetto si imparerà come costruire una banca immaginaria. Queste lezioni includono istruzioni su come disporre un'app web e fornire rotte, creare form, gestire lo stato e recuperare dati da un'API da cui è possibile ottenere i dati della banca.
|
||||
|
||||
|  |  |
|
||||
|--------------------------------|--------------------------------|
|
||||
|
||||
## Lezioni
|
||||
|
||||
1. [Modelli HTML e Rotte in un'app Web](../1-template-route/translations/README.it.md)
|
||||
2. [Creare un Form di Accesso e Registrazione](../2-forms/translations/README.it.md)
|
||||
3. [Metodi di Recupero e Utilizzo Dati](../3-data/translations/README.it.md)
|
||||
4. [Concetti di Gestione dello Stato](../4-state-management/translations/README.it.md)
|
||||
|
||||
### Crediti
|
||||
|
||||
Queste lezioni sono state scritte con il :hearts: da [Yohan Lasorsa](https://twitter.com/sinedied).
|
||||
|
||||
Se interessati a imparare come costruire l' [API del server](../../api/tranlations/README.it.md) utilizzata in queste lezioni, è possibile seguire [questa serie di video](https://aka.ms/NodeBeginner) (in particolare i video da 17 a 21).
|
||||
|
||||
Si può anche dare un'occhiata a [questo tutorial interattivo di apprendimento](https://aka.ms/learn/express-api).
|
@@ -1,21 +0,0 @@
|
||||
# :dollar: 銀行を作る
|
||||
|
||||
このプロジェクトでは、架空の銀行を構築する方法を学びます。これらのレッスンでは、Web アプリのレイアウト方法やルートの提供、フォームの構築、状態の管理、銀行のデータを API からフェッチする方法などを説明します。
|
||||
|
||||
|  |  |
|
||||
|--------------------------------|--------------------------------|
|
||||
|
||||
## レッスン
|
||||
|
||||
1. [Web アプリの HTML テンプレートとルート](../1-template-route/translations/README.ja.md)
|
||||
2. [ログインと登録フォームの構築](../2-forms/translations/README.ja.md)
|
||||
3. [データの取得と利用方法](../3-data/translations/README.ja.md)
|
||||
4. [状態管理の概念](../4-state-management/translations/README.ja.md)
|
||||
|
||||
### クレジット
|
||||
|
||||
These lessons were written with :hearts: by [Yohan Lasorsa](https://twitter.com/sinedied).
|
||||
|
||||
これらのレッスンで使用した [server API](../api/translations/README.ja) の構築方法を学びたい方は、[このシリーズの動画](https://aka.ms/NodeBeginner) をご覧ください (特に動画17~21)。
|
||||
|
||||
また、[このインタラクティブな学習チュートリアル](https://aka.ms/learn/express-api) もご覧ください。
|
@@ -1,21 +0,0 @@
|
||||
# :dollar: 은행 만들기
|
||||
|
||||
이 프로젝트에서는, 가상의 은행을 만드는 방법을 배웁니다. 이 강의에는 웹 앱을 레이아웃과 라우트를 제공하고, 폼을 작성하며 상태를 관리하고, 은행 데이터 API에서 데이터를 가져오는 방법에 대한 지침이 포함되어 있습니다.
|
||||
|
||||
|  |  |
|
||||
|-----------------------------------|-----------------------------------|
|
||||
|
||||
## 강의
|
||||
|
||||
1. [웹 앱에서 HTML 템플릿과 라우트](../1-template-route/translations/README.ko.md)
|
||||
2. [로그인과 가입 폼 제작](../2-forms/translations/README.ko.md)
|
||||
3. [데이터를 가져오고 사용하는 방식](../3-data/translations/README.ko.md)
|
||||
4. [상태 관리의 개념](../4-state-management/translations/README.ko.md)
|
||||
|
||||
### 크레딧
|
||||
|
||||
강의를 제작한 분 : :hearts: by [Yohan Lasorsa](https://twitter.com/sinedied)
|
||||
|
||||
강의에 사용된 서버 API 만드는 것이 궁금하시면 [YouTube 영상](https://aka.ms/NodeBeginner)을 참고하세요. (17번 ~ 21번 영상 주목)
|
||||
|
||||
Node.js와 Express로 웹 API를 만드는 참고 자료는 [여기](https://aka.ms/learn/express-api).
|
@@ -1,21 +0,0 @@
|
||||
# :dollar: ഒരു ബാങ്ക് നിർമ്മിക്കുക
|
||||
|
||||
ഈ പ്രോജക്റ്റിൽ, ഒരു സാങ്കൽപ്പിക ബാങ്ക് എങ്ങനെ നിർമ്മിക്കാമെന്ന് നിങ്ങൾ പഠിക്കും. ഈ പാഠങ്ങളിൽ ഒരു വെബ് ആപ്പ് എങ്ങനെ ലേഔട്ട് ചെയ്യാം, റൂട്ടുകൾ നൽകാം, ഫോമുകൾ നിർമ്മിക്കാം, സ്റ്റേറ്റ് മാനേജുചെയ്യാം, നിങ്ങൾക്ക് ബാങ്കിന്റെ ഡാറ്റ ലഭ്യമാക്കാൻ കഴിയുന്ന ഒരു API-ൽ നിന്ന് ഡാറ്റ നേടുന്നത് എങ്ങനെ എന്നതിനുള്ള നിർദ്ദേശങ്ങൾ ഉൾപ്പെടുന്നു.
|
||||
|
||||
|  |  |
|
||||
|--------------------------------|--------------------------------|
|
||||
|
||||
## പാഠങ്ങൾ
|
||||
|
||||
1. [ഒരു വെബ് ആപ്പിലെ HTML ടെംപ്ലേറ്റുകളും റൂട്ടുകളും](1-template-route/README.md)
|
||||
2. [ഒരു ലോഗിൻ, രജിസ്ട്രേഷൻ ഫോം നിർമ്മിക്കുക](2-forms/README.md)
|
||||
3. [ഡാറ്റ ലഭ്യമാക്കുന്നതിനും ഉപയോഗിക്കുന്നതിനുമുള്ള രീതികൾ](3-data/README.md)
|
||||
4. [സംസ്ഥാന മാനേജ്മെന്റിന്റെ ആശയങ്ങൾ](4-state-management/README.md)
|
||||
|
||||
### ക്രെഡിറ്റുകൾ
|
||||
|
||||
ഈ പാഠങ്ങൾ എഴുതിയത് :hearts: [Yohan Lasorsa](https://twitter.com/sinedied).
|
||||
|
||||
ഈ പാഠങ്ങളിൽ ഉപയോഗിച്ചിരിക്കുന്ന [സെർവർ API](/7-bank-project/api/README.md) എങ്ങനെ നിർമ്മിക്കാമെന്ന് അറിയാൻ നിങ്ങൾക്ക് താൽപ്പര്യമുണ്ടെങ്കിൽ, നിങ്ങൾക്ക് [ഈ വീഡിയോ പരമ്പര](https://aka.ms/NodeBeginner) (പ്രത്യേകിച്ച് 17 മുതൽ 21 വരെയുള്ള വീഡിയോകൾ).
|
||||
|
||||
നിങ്ങൾക്ക് [ഈ ഇന്ററാക്ടീവ് ലേൺ ട്യൂട്ടോറിയൽ](https://aka.ms/learn/express-api) നോക്കാവുന്നതാണ്.
|
@@ -1,21 +0,0 @@
|
||||
# :dollar: Bina Bank
|
||||
|
||||
Dalam projek ini, anda akan belajar membina bank fiksyen. Pelajaran ini merangkumi petunjuk bagaimana mengatur aplikasi web dan menyediakan laluan, membina borang, mengurus keadaan, dan mengambil data dari API dari mana anda dapat mengambil data bank.
|
||||
|
||||
|  |  |
|
||||
|--------------------------------|--------------------------------|
|
||||
|
||||
## Pembelajaran
|
||||
|
||||
1. [Templat dan Laluan HTML dalam Aplikasi Web](../1-template-route/README.md)
|
||||
2. [Bina Log Masuk dan Borang Pendaftaran](../2-forms/README.md)
|
||||
3. [Kaedah Mengambil dan Menggunakan Data](../3-data/README.md)
|
||||
4. [Konsep Pengurusan Negeri](../4-state-management/README.md)
|
||||
|
||||
### Kredit
|
||||
|
||||
Pelajaran ini ditulis dengan :hearts: oleh [Yohan Lasorsa](https://twitter.com/sinedied).
|
||||
|
||||
Sekiranya anda berminat untuk mempelajari cara membina [server API](./api/README) yang digunakan dalam pelajaran ini, anda boleh mengikuti [rangkaian video ini](https://aka.ms/NodeBeginner) (khususnya video 17 hingga 21).
|
||||
|
||||
Anda juga boleh melihat [tutorial Belajar interaktif ini](https://aka.ms/learn/express-api).
|
@@ -1,21 +0,0 @@
|
||||
# :dollar: Bouw een bank
|
||||
|
||||
In dit project leert u hoe u een fictieve bank bouwt. Deze lessen bevatten instructies voor het opmaken van een web-app en het aanbieden van routes, het bouwen van formulieren, het beheren van de staat en het ophalen van gegevens uit een API waaruit u de gegevens van de bank kunt ophalen.
|
||||
|
||||
|  |  |
|
||||
|--------------------------------|--------------------------------|
|
||||
|
||||
## Lessen
|
||||
|
||||
1. [HTML-sjablonen en routes in een webapp](../1-template-route/translations/README.nl.md)
|
||||
2. [Bouw een aanmeldings- en registratieformulier](../2-forms/translations/README.nl.md)
|
||||
3. [Methoden voor het ophalen en gebruiken van gegevens](../3-data/translations/README.nl.md)
|
||||
4. [Concepten van staatsbeheer](../4-state-management/translations/README.nl.md)
|
||||
|
||||
### Credits
|
||||
|
||||
Deze lessen zijn geschreven met :hearts: door [Yohan Lasorsa](https://twitter.com/sinedied).
|
||||
|
||||
Als u geïnteresseerd bent om te leren hoe u de [server API](../api/translations/README.nl.md) bouwt die in deze lessen wordt gebruikt, kunt u [deze reeks video's](https://aka.ms/NodeBeginner) volgen (in het bijzonder video's 17 t/m 21).
|
||||
|
||||
U kunt ook een kijkje nemen op [deze interactieve Leer-tutorial](https://aka.ms/learn/express-api).
|
@@ -1,21 +0,0 @@
|
||||
# :dollar: Construindo um banco
|
||||
|
||||
Neste projeto, você aprenderá a construir um banco fictício. Essas lições incluem instruções sobre como fazer o layout de um aplicativo Web, fornecer rotas, criar formulários, gerenciar o estado e buscar dados de uma API da qual você buscará os dados do banco.
|
||||
|
||||
|  |  |
|
||||
|--------------------------------|--------------------------------|
|
||||
|
||||
## Lições
|
||||
|
||||
1. [Templates HTML e Routes da aplicação Web](1-template-route/README.md)
|
||||
2. [Crie um formulário de login e registro](2-forms/README.md)
|
||||
3. [Métodos de busca e uso de dados](3-data/README.md)
|
||||
4. [Conceitos de Gestão do Estado](4-state-management/README.md)
|
||||
|
||||
### Créditos
|
||||
|
||||
Essas lições foram escritas com :hearts: por [Yohan Lasorsa](https://twitter.com/sinedied).
|
||||
|
||||
Se você estiver interessado em aprender como construir uma [server API](/7-bank-project/api/README.md) usado nestas lições, você pode seguir [esta série de vídeos](https://aka.ms/NodeBeginner) (em particular os vídeos 17 à 21).
|
||||
|
||||
Você também pode dar uma olhada [neste tutorial de aprendizagem interativa.](https://aka.ms/learn/express-api).
|
@@ -1,22 +0,0 @@
|
||||
# :dollar: வங்கி கட்டவும்
|
||||
|
||||
இந்த திட்டத்தில், ஒரு கற்பனை வங்கியை எவ்வாறு உருவாக்குவது என்பதை நீங்கள் கற்றுக்கொள்வீர்கள். இந்த பாடங்களில் ஒரு வலை பயன்பாட்டை எவ்வாறு வடிவமைப்பது மற்றும் வழிகளை வழங்குவது, படிவங்களை உருவாக்குவது, மாநிலத்தை நிர்வகிப்பது மற்றும் வங்கியின் தரவை நீங்கள் பெறக்கூடிய ஏபிஐ யிலிருந்து தரவைப் பெறுதல் ஆகியவை அடங்கும்.
|
||||
|
||||
|  |  |
|
||||
|--------------------------------|--------------------------------|
|
||||
|
||||
## பாடங்கள்
|
||||
|
||||
1. [ஹெச்டிஎம்எல் வார்ப்புருக்கள் மற்றும் வலை பயன்பாட்டில் வழிகள்](../1-template-route/README.md)
|
||||
2. [உள்நுழைவு மற்றும் பதிவு படிவத்தை உருவாக்கவும்](../2-forms/README.md)
|
||||
3. [தரவுகளை ப் பெறுதல் மற்றும் பயன்படுத்துதல் முறைகள்](../3-data/README.md)
|
||||
4. [நிலை மேலாண்மை பற்றிய கருத்துக்கள்](../4-state-management/README.md)
|
||||
|
||||
### கடன்கள்
|
||||
|
||||
இந்த பாடங்களை ♥️ எழுதினார் [யோஹான் லசோர்சா](https://twitter.com/sinedied).
|
||||
|
||||
இந்தப் பாடங்களில் பயன்படுத்தப்படும் [சர்வர் ஏபிஐ](../../7-bank-project/api/README.md) எவ்வாறு உருவாக்குவது என்பதைக் கற்றுக்கொள்ள நீங்கள் ஆர்வமாக இருந்தால், நீங்கள் [வீடியோக்களின் இந்த தொடர்](https://aka.ms/NodeBeginner) (குறிப்பாக வீடியோக்கள் 17 முதல் 21 வரை) பின்பற்றலாம்.
|
||||
|
||||
|
||||
நீங்கள் ஒரு பார்வை பார்க்கலாம் [இந்த ஊடாடும் கற்றல் பயிற்சி](https://aka.ms/learn/express-api).
|
@@ -1,21 +0,0 @@
|
||||
# :dollar: 建立银行
|
||||
|
||||
在这个项目中,你将学习如何建立一个虚构的银行。这些课程包括指导如何布局网络应用和提供路由,构建表单,管理状态,以及从一个 API 中获取银行的数据。
|
||||
|
||||
|  |  |
|
||||
| ------------------------------- | ------------------------------- |
|
||||
|
||||
## 课程
|
||||
|
||||
1. [HTML 模版与网页路由](../1-template-route/translations/README.md)
|
||||
2. [建立登陆与注册表单](../2-forms/translations/README.md)
|
||||
3. [取得并使用资料](../3-data/translations/README.md)
|
||||
4. [状态管理的概念](../4-state-management/translations/README.md)
|
||||
|
||||
### 贡献者
|
||||
|
||||
这些课程是由 [Yohan Lasorsa](https://twitter.com/sinedied) 用满满的 ♥️ 來编写。
|
||||
|
||||
如果你有兴趣建立本课程使用的 [服务器 API](../api/translations/README.zh-tw.md),你可以遵循[这一系列的视频](https://aka.ms/NodeBeginner),特别是视频 17 到 21。
|
||||
|
||||
你也可以访问[这款互动式教学网站](https://aka.ms/learn/express-api)。
|
@@ -1,21 +0,0 @@
|
||||
# :dollar: 建立銀行
|
||||
|
||||
在這個專案中,你會學習如何建立虛擬銀行。這些課程包含許多教程:設計網頁應用程式的格式、提供網頁路由、建立表單、管理狀態和利用 API 抓取銀行的資料。
|
||||
|
||||
|  |  |
|
||||
|---------------------------------|-----------------------------------|
|
||||
|
||||
## 課程
|
||||
|
||||
1. [HTML 模板與網頁路由](../1-template-route/translations/README.md)
|
||||
2. [建立登入與註冊表單](../2-forms/translations/README.md)
|
||||
3. [取得並使用資料](../3-data/translations/README.md)
|
||||
4. [狀態控管的概念](../4-state-management/translations/README.md)
|
||||
|
||||
### 參與人員
|
||||
|
||||
這些課程是由 [Yohan Lasorsa](https://twitter.com/sinedied) 用滿滿的 ♥️ 來編寫。
|
||||
|
||||
如果你有興趣建立本課程使用的[伺服器 API](../api/translations/README.zh-tw.md),你可以遵循[這一系列的影片](https://aka.ms/NodeBeginner),特別是影片 17 至 21。
|
||||
|
||||
你也可以造訪[這款互動式教學網站](https://aka.ms/learn/express-api)。
|
Reference in New Issue
Block a user