Update Translations

This commit is contained in:
Lee Stott
2025-08-23 20:47:28 +01:00
committed by GitHub
parent eff26d962b
commit 4c689557b5
497 changed files with 0 additions and 50930 deletions

View File

@@ -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)

View File

@@ -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 lutilisateur sélectionne un lien ou soumet des données à laide dun 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 laction actuelle de lutilisateur et limitant les interactions pendant le rechargement. Ce flux de travail est également appelé *Application multipage* ou *AMP*.
![Mettre à jour le flux de travail dans une application multipage](../images/mpa.png)
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 denvoyer et de récupérer des données à partir dun serveur de manière asynchrone à laide 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 à laide de lAPI [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model). Au fil du temps, cette approche a évolué pour devenir ce quon appelle maintenant une [*Application dune seule page* ou *SPA*](https://en.wikipedia.org/wiki/Single-page_application).
![Mettre à jour le flux de travail dans une application dune seule page](../images/spa.png)
Lors de lintroduction dAJAX, 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 [lAPI `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 lAPI Fetch, si vous souhaitez que votre application Web fonctionne sur des navigateurs hérités ou anciens, il est toujours judicieux de vérifier dabord 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 dinscription pour créer un compte. Nous allons maintenant ajouter du code pour vous connecter à laide dun 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 dutilisateur à partir de lentrée avec `loginForm.user.value`. Chaque contrôle de formulaire est accessible par son nom (défini dans le code HTML à laide de lattribut `name`) en tant que propriété du formulaire.
De la même manière que nous avons fait pour lenregistrement, 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 lAPI `fetch` pour demander les données de manière asynchrone au serveur, mais cette fois, nous navons pas besoin de paramètres supplémentaires autres que lURL à appeler, car nous ninterrogeons 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 nappelons pas cette fonction et nutilisons pas directement la valeur `user` dans lURL?
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 dabord, 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 derreur. Pour linstant, nous allons seulement ajouter un message de journal pour afficher lerreur, 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` nexiste 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 à laide 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 sagit dun mécanisme de sécurité appliqué par les navigateurs. Mais attendez, notre application web sexécute sur 'localhost:3000' alors que lAPI du serveur sexé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 deffectuer 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 lafficher. 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 dun é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, cest aussi une méthode efficace pour supprimer tous les enfants dun é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) dun é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 dutilisateur qui nexiste pas, un message saffiche dans la console, mais pour un utilisateur normal, rien ne change et vous ne savez pas ce qui se passe.
Ajoutons un élément despace réservé dans le formulaire de connexion où nous pouvons afficher un message derreur 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 ny 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 dassistance `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 derreur 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:
![Capture décran montrant le message derreur affiché lors de la connexion](../images/login-error.png)
Maintenant, nous avons un texte derreur qui apparaît visuellement, mais si vous lessayez avec un lecteur décran, vous remarquerez que rien nest 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` (noubliez 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 dafficher 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 despace 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 limportance de [structure de titre](https://www.nomensa.com/blog/2017/how-structure-headings-web-accessibility) pour laccessibilité, 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 lespace 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 dabord, nous vérifions que nous avons les données de compte dont nous avons besoin avant daller plus loin. Ensuite, nous utilisons la fonction `updateElement()` que nous avons créée précédemment pour mettre à jour le code HTML.
> Pour rendre laffichage 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 laffichage 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 limplé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 dune 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* dune 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 à laide 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 à laide de données de transaction. Nous lutiliserons 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 lattacher à 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 dattacher 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 dutilisation.
Si vous essayez dutiliser 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:
![Capture d'écran d'un exemple de résultat du tableau de bord après le style](../../images/screen2.png)
## 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)

View File

@@ -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 पृष्ठ लौटाता है जिसे ब्राउज़र द्वारा संसाधित करने की आवश्यकता होती है, वर्तमान उपयोगकर्ता कार्रवाई को बाधित करता है और पुनः लोड के दौरान इंटरैक्शन को सीमित करता है। इस वर्कफ़्लो को *मल्टी-पेज एप्लिकेशन* या *एमपीए* भी कहा जाता है।
![मल्टी-पेज एप्लिकेशन में वर्कफ़्लो अपडेट करें](../images/mpa.png)
जब वेब एप्लिकेशन अधिक जटिल और संवादात्मक होने लगे, तो [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) कहलाता है।
![एकल-पृष्ठ एप्लिकेशन में वर्कफ़्लो अपडेट करें](../images/spa.png)
जब 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);
}
```
अब यदि आप अमान्य खाते से लॉगिन करने का प्रयास करते हैं, तो आपको कुछ इस तरह से देखना चाहिए:
![लॉगिन के दौरान प्रदर्शित त्रुटि संदेश दिखाने वाला स्क्रीनशॉट](../images/login-error.png)
अब हमारे पास त्रुटि पाठ है जो नेत्रहीन रूप से दिखाई देता है, लेकिन यदि आप इसे एक स्क्रीन रीडर के साथ आज़माते हैं तो आप देखेंगे कि कुछ भी घोषित नहीं हुआ है। पाठ पाठकों के लिए गतिशील रूप से एक पृष्ठ में जोड़े जाने की घोषणा के लिए, इसे [लाइव रीजन](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) का उपयोग करने का प्रयास करें।
यहाँ एक सत्यलेड डैशबोर्ड पृष्ठ का उदाहरण दिया गया है:
![
स्टाइल के बाद डैशबोर्ड के उदाहरण परिणाम का स्क्रीनशॉट](../../images/screen2.png)
## व्याख्यान उपरांत प्रश्नोत्तरी
[व्याख्यान उपरांत प्रश्नोत्तरी](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/46?loc=hi)
## असाइनमेंट
[Refactor and comment your code](assignment.hi.md)

View File

@@ -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) .
![Aggiornare il flusso di lavoro in un'applicazione multipagina](../images/mpa.png)
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).
![Aggiornare il flusso di lavoro in un'applicazione a pagina singola](../images/spa.png)
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:
![Videata che mostra il messaggio di errore visualizzato durante l'accesso](../images/login-error.png)
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:
![Videata di un risultato di esempio del cruscotto dopo l'applicazione dello stile](../../images/screen2.png)
## 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)

View File

@@ -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* とも呼ばれます。
![複数ページのアプリケーションでワークフローを更新する](../images/mpa.png)
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) と呼ばれているものへと発展してきました。
![シングルページアプリケーションでワークフローを更新](../images/spa.png)
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);
}
```
無効なアカウントでログインしようとすると、このように表示されるはずです。
![ログイン中に表示されるエラーメッセージを示すスクリーンショット](../images/login-error.png)
これで、視覚的にはエラーテキストが表示されるようになりましたが、スクリーンリーダーで試してみると、何もアナウンスされないことに気づくでしょう。ページに動的に追加されたテキストをスクリーンリーダーでアナウンスするには、[ライブリージョン](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)を作成してみてください。
ダッシュボードページのスタイリング例です。
![スタイリング後のダッシュボードの結果例のスクリーンショット](../../images/screen2.png)
## レッスン後の小テスト
[レッスン後の小テスト](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/46?loc=ja)
## 課題
[コードのリファクタとコメント](assignment.ja.md)

View File

@@ -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*라고 합니다.
![Update workflow in a multi-page application](.././images/mpa.png)
웹 애플리케이션이 더 복잡해지고 상호 작용하기 시작하면서, [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)라는 것으로 발전했습니다.
![Update workflow in a single-page application](.././images/spa.png)
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);
}
```
이제 유효하지 않은 계정으로 로그인 시도하면, 다음과 같이 보입니다:
![Screenshot showing the error message displayed during login](.././images/login-error.png)
`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)으로 만들어보세요.
여기는 꾸며진 대시보드 페이지의 예시입니다:
![Screenshot of an example result of the dashboard after styling](../../images/screen2.png)
## 강의 후 퀴즈
[Post-lecture quiz](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/46?loc=ko)
## 과제
[Refactor and comment your code](../assignment.md)

View File

@@ -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*.
![Kemas kini aliran kerja dalam aplikasi berbilang halaman](./images/mpa.png)
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).
![Kemas kini alur kerja dalam aplikasi satu halaman](./images/spa.png)
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:
![Tangkapan skrin yang menunjukkan mesej ralat yang ditunjukkan semasa log masuk](../images/login-error.png)
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:
![Tangkapan skrin hasil contoh papan pemuka selepas menggayakan](../../images/screen2.png)
## Kuiz Pasca Kuliah
[Kuiz Pasca Kuliah](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/46)
## Tugasan
[Refaktor dan komen kod anda](assignment.ms.md)

View File

@@ -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*
![多頁面應用程式的更新流程](../images/mpa.png)
網頁應用程式變得更加複雜,促使新的技術問世:[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)。
![單一頁面應用程式的更新流程](../images/spa.png)
在 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);
}
```
現在,試著以不合法的帳戶進行登入,你應該能看到像這樣的畫面:
![登入出現錯誤訊息之截圖](../images/login-error.png)
現在我們印出錯誤訊息,但螢幕報讀器並沒有做任何報讀。為了讓被動態加入的文字能被螢幕報讀器閱讀出來,我們需要使用 [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),它能完美地呈現在電腦或是行動裝置上。
這邊有造型過後的儀表板例子:
![造型化後的儀表板截圖](../../images/screen2.png)
## 課後測驗
[課後測驗](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/46?loc=zh_tw)
## 作業
[重構並註解你的程式碼](assignment.zh-tw.md)

View File

@@ -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. |

View File

@@ -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é.|

View File

@@ -1,15 +0,0 @@
# अपना कोड रिफैक्टर और टिप्पणी करें
## अनुदेश
जैसे-जैसे आपका कोडबेस बढ़ता है, समय के साथ-साथ पठनीय और बनाए रखने के लिए अपने कोड को बार-बार रिफलेक्टर करना महत्वपूर्ण है। टिप्पणी जोड़ें और कोड की गुणवत्ता में सुधार करने के लिए अपने `app.js` को फिर से फ़िल्टर करें:
- सर्वर API बेस URL की तरह, स्थिरांक निकालें
- समान कोड को फैक्टराइज़ करें: उदाहरण के लिए आप दोनों `createAccount()` और `getAccount()` में उपयोग किए गए कोड को फिर से इकट्ठा करने के लिए एक `sendRequest()` फ़ंक्शन बना सकते हैं।
- कोड को पढ़ने, और टिप्पणी जोड़ने में आसान बनाने के लिए इसे पुनर्गठित करें
## शीर्ष
| मानदंड | उदाहरणात्मक | पर्याप्त | सुधार की जरूरत |
| ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| | कोड टिप्पणी की गई है, विभिन्न वर्गों में सुव्यवस्थित और पढ़ने में आसान है। स्थिरांक निकाले जाते हैं और एक कारक `sendRequest()` फ़ंक्शन बनाया गया है. | कोड साफ है लेकिन फिर भी अधिक टिप्पणियों, निरंतर निष्कर्षण या कारक के साथ सुधार किया जा सकता है. | कोड गड़बड़ है, टिप्पणी नहीं की गई है, स्थिरांक नहीं निकाले गए हैं और कोड कारक नहीं है. |

View File

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

View File

@@ -1,15 +0,0 @@
# コードのリファクタとコメント
## 説明書
コードベースが大きくなってくると、読みやすく保守性の高いコードを維持するために、頻繁にリファクタリングを行うことが重要になってきます。コメントを追加して `app.js` をリファクタリングし、コードの品質を向上させましょう。
- サーバー API のベース URL のような定数を抽出します
- 類似したコードの因数分解: 例えば、`sendRequest()` 関数を作成して `createAccount()``getAccount()` の両方で使用するコードを再グループ化することができます
- 読みやすいようにコードを再編成し、コメントを追加します
## ルーブリック
| 基準 | 模範的な例 | 適切な | 改善が必要 |
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| | コードはコメントされており、さまざまなセクションでよく整理されており、読みやすくなっています。定数が抽出され、因数分解された `sendRequest()` 関数が作成されています。 | コードはクリーンですが、より多くのコメント、定数抽出、因数分解などで改善することができます。 | コードは乱雑で、コメントされておらず、定数は抽出されておらず、コードは因数分解されていません。 |

View File

@@ -1,15 +0,0 @@
# 코드 리팩터링 및 주석
## 설명
코드베이스가 커짐에 따라 코드를 자주 리팩터링하여 시간이 지남에 따라 가독성과 유지 관리가 가능하도록 유지하는 것이 중요합니다. 주석을 추가하고 `app.js` 를 리팩터링하여 코드 품질을 개선합니다.
- 서버 API 기본 URL과 같은 상수 추출
- 유사한 코드를 분해합니다. 예를 들어 `createAccount()``getAccount()` 모두에서 사용되는 코드를 다시 그룹화하는 `sendRequest()` 함수를 만들 수 있습니다.
- 읽기 쉽도록 코드 재구성 및 주석 추가
## 평가 기준
기준 | 모범 답안 | 적당한 답안 | 개선이 필요한 답안
--- | --- | --- | ---
| 코드 주석이 잘 짜여진 여러개의 섹션으로 나뉘며 읽기 쉬운 경우. 그리고 상수들이 추출되고 잘 쪼개진 `sendRequest()` 함수를 생성한 경우 | 코드는 깔끔하지만 더 많은 주석, 지속적인 추출 또는 분해를 통해 개선될 수 있는 경우 | 코드가 지저분하고 주석도 잘 작성하지 않았으며 상수 또한 추출되지 않고 코드도 분해되지 않은 경우

View File

@@ -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. |

View File

@@ -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. |

View File

@@ -1,15 +0,0 @@
# 重構並註解你的程式碼
## 簡介
正當你的程式碼越來越多時,頻繁地重構你的程式,讓程式碼容易去閱讀與維護變得十分重要。加入一些註解並重構檔案 `app.js` 來增進檔案的品質:
- 取出常數,好比說伺服器 API 的根網址
- 重構相同的程式碼:舉例來說,你可以建立函式 `sendRequest()` 來合併 `createAccount()``getAccount()`
- 重新編排你的程式和加入註解,讓它更容易地閱讀。
## 學習評量
| 作業內容 | 優良 | 普通 | 待改進 |
| -------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------- |
| | 程式碼有做註解,分塊地整理好。常數有被取出來且函式 `sendRequest()` 已設定完成 | 程式碼有做過處理,但仍可以藉由加入註解、排版與重構來增進品質 | 程式碼很雜亂,缺乏註解,常數與函式並沒有做規劃 |