You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Web-Dev-For-Beginners/translations/es/7-bank-project/3-data
leestott 7cfaffabb5
🌐 Update translations via Co-op Translator
3 weeks ago
..
README.md 🌐 Update translations via Co-op Translator 3 weeks ago
assignment.md 🌐 Update translations via Co-op Translator 4 weeks ago

README.md

Construir una App Bancaria Parte 3: Métodos para Obtener y Usar Datos

Cuestionario Previo a la Clase

Cuestionario previo a la clase

Introducción

En el núcleo de cada aplicación web está la información. La información puede tomar muchas formas, pero su propósito principal siempre es mostrar datos al usuario. Con las aplicaciones web volviéndose cada vez más interactivas y complejas, cómo el usuario accede e interactúa con la información es ahora una parte clave del desarrollo web.

En esta lección, veremos cómo obtener datos de un servidor de manera asincrónica y usar estos datos para mostrar información en una página web sin recargar el HTML.

Prerrequisitos

Necesitas haber construido la parte de Formulario de Inicio de Sesión y Registro de la aplicación web para esta lección. También necesitas instalar Node.js y ejecutar la API del servidor localmente para obtener datos de cuentas.

Puedes probar que el servidor está funcionando correctamente ejecutando este comando en una terminal:

curl http://localhost:5000/api
# -> should return "Bank API v1.0.0" as a result

AJAX y obtención de datos

Los sitios web tradicionales actualizan el contenido mostrado cuando el usuario selecciona un enlace o envía datos mediante un formulario, recargando la página HTML completa. Cada vez que se necesitan cargar nuevos datos, el servidor web devuelve una página HTML completamente nueva que debe ser procesada por el navegador, interrumpiendo la acción actual del usuario y limitando las interacciones durante la recarga. Este flujo de trabajo también se conoce como una Aplicación de Múltiples Páginas o MPA.

Flujo de actualización en una aplicación de múltiples páginas

Cuando las aplicaciones web comenzaron a volverse más complejas e interactivas, surgió una nueva técnica llamada AJAX (JavaScript y XML Asíncronos). Esta técnica permite que las aplicaciones web envíen y recuperen datos de un servidor de manera asincrónica usando JavaScript, sin tener que recargar la página HTML, lo que resulta en actualizaciones más rápidas e interacciones más fluidas. Cuando se reciben nuevos datos del servidor, la página HTML actual también puede ser actualizada con JavaScript usando la API del DOM. Con el tiempo, este enfoque ha evolucionado hacia lo que ahora se llama una Aplicación de Página Única o SPA.

Flujo de actualización en una aplicación de página única

Cuando se introdujo AJAX por primera vez, la única API disponible para obtener datos de manera asincrónica era XMLHttpRequest. Sin embargo, los navegadores modernos ahora también implementan la más conveniente y poderosa Fetch API, que utiliza promesas y es más adecuada para manipular datos JSON.

Aunque todos los navegadores modernos admiten la Fetch API, si deseas que tu aplicación web funcione en navegadores antiguos o heredados, siempre es una buena idea verificar primero la tabla de compatibilidad en caniuse.com.

Tarea

En la lección anterior implementamos el formulario de registro para crear una cuenta. Ahora agregaremos código para iniciar sesión usando una cuenta existente y obtener sus datos. Abre el archivo app.js y agrega una nueva función login:

async function login() {
  const loginForm = document.getElementById('loginForm')
  const user = loginForm.user.value;
}

Aquí comenzamos recuperando el elemento del formulario con getElementById(), y luego obtenemos el nombre de usuario del campo de entrada con loginForm.user.value. Cada control de formulario puede ser accedido por su nombre (establecido en el HTML usando el atributo name) como una propiedad del formulario.

De manera similar a lo que hicimos para el registro, crearemos otra función para realizar una solicitud al servidor, pero esta vez para obtener los datos de la cuenta:

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' };
  }
}

Usamos la API fetch para solicitar los datos de manera asincrónica al servidor, pero esta vez no necesitamos ningún parámetro adicional aparte de la URL que queremos llamar, ya que solo estamos consultando datos. Por defecto, fetch crea una solicitud HTTP GET, que es lo que buscamos aquí.

encodeURIComponent() es una función que escapa caracteres especiales para URLs. ¿Qué problemas podríamos tener si no llamamos a esta función y usamos directamente el valor de user en la URL?

Ahora actualicemos nuestra función login para usar getAccount:

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');
}

Primero, como getAccount es una función asincrónica, necesitamos usar la palabra clave await para esperar el resultado del servidor. Como con cualquier solicitud al servidor, también tenemos que manejar los casos de error. Por ahora, solo agregaremos un mensaje de registro para mostrar el error y volveremos a esto más adelante.

Luego, tenemos que almacenar los datos en algún lugar para poder usarlos más tarde y mostrar la información del tablero. Dado que la variable account aún no existe, crearemos una variable global para ello al principio de nuestro archivo:

let account = null;

Después de que los datos del usuario se guarden en una variable, podemos navegar desde la página de login al dashboard usando la función navigate() que ya tenemos.

Finalmente, necesitamos llamar a nuestra función login cuando se envíe el formulario de inicio de sesión, modificando el HTML:

<form id="loginForm" action="javascript:login()">

Prueba que todo funcione correctamente registrando una nueva cuenta e intentando iniciar sesión con la misma cuenta.

Antes de pasar a la siguiente parte, también podemos completar la función register agregando esto al final de la función:

account = result;
navigate('/dashboard');

¿Sabías que por defecto solo puedes llamar a APIs del servidor desde el mismo dominio y puerto que la página web que estás viendo? Este es un mecanismo de seguridad impuesto por los navegadores. Pero espera, nuestra aplicación web se ejecuta en localhost:3000 mientras que la API del servidor se ejecuta en localhost:5000, ¿por qué funciona? Usando una técnica llamada Intercambio de Recursos de Origen Cruzado (CORS), es posible realizar solicitudes HTTP de origen cruzado si el servidor agrega encabezados especiales a la respuesta, permitiendo excepciones para dominios específicos.

Aprende más sobre APIs tomando esta lección

Actualizar HTML para mostrar datos

Ahora que tenemos los datos del usuario, tenemos que actualizar el HTML existente para mostrarlos. Ya sabemos cómo recuperar un elemento del DOM usando, por ejemplo, document.getElementById(). Después de tener un elemento base, aquí hay algunas APIs que puedes usar para modificarlo o agregar elementos secundarios:

  • Usando la propiedad textContent puedes cambiar el texto de un elemento. Ten en cuenta que cambiar este valor elimina todos los elementos secundarios del elemento (si los hay) y los reemplaza con el texto proporcionado. Por lo tanto, también es un método eficiente para eliminar todos los elementos secundarios de un elemento dado asignando una cadena vacía '' a él.

  • Usando document.createElement() junto con el método append() puedes crear y adjuntar uno o más nuevos elementos secundarios.

Usando la propiedad innerHTML de un elemento también es posible cambiar su contenido HTML, pero esta debería evitarse ya que es vulnerable a ataques de cross-site scripting (XSS).

Tarea

Antes de pasar a la pantalla del tablero, hay una cosa más que deberíamos hacer en la página de login. Actualmente, si intentas iniciar sesión con un nombre de usuario que no existe, se muestra un mensaje en la consola, pero para un usuario normal nada cambia y no sabes qué está pasando.

Agreguemos un elemento de marcador de posición en el formulario de inicio de sesión donde podamos mostrar un mensaje de error si es necesario. Un buen lugar sería justo antes del botón de inicio de sesión <button>:

...
<div id="loginError"></div>
<button>Login</button>
...

Este elemento <div> está vacío, lo que significa que no se mostrará nada en la pantalla hasta que agreguemos contenido. También le damos un id para poder recuperarlo fácilmente con JavaScript.

Vuelve al archivo app.js y crea una nueva función auxiliar updateElement:

function updateElement(id, text) {
  const element = document.getElementById(id);
  element.textContent = text;
}

Esta es bastante sencilla: dado un id de elemento y un texto, actualizará el contenido de texto del elemento del DOM con el id correspondiente. Usemos este método en lugar del mensaje de error anterior en la función login:

if (data.error) {
  return updateElement('loginError', data.error);
}

Ahora, si intentas iniciar sesión con una cuenta inválida, deberías ver algo como esto:

Captura de pantalla mostrando el mensaje de error durante el inicio de sesión

Ahora tenemos un texto de error que aparece visualmente, pero si lo intentas con un lector de pantalla notarás que no se anuncia nada. Para que el texto que se agrega dinámicamente a una página sea anunciado por los lectores de pantalla, necesitará usar algo llamado Región Viva. Aquí vamos a usar un tipo específico de región viva llamada alerta:

<div id="loginError" role="alert"></div>

Implementa el mismo comportamiento para los errores de la función register (no olvides actualizar el HTML).

Mostrar información en el tablero

Usando las mismas técnicas que acabamos de ver, también nos encargaremos de mostrar la información de la cuenta en la página del tablero.

Así es como se ve un objeto de cuenta recibido del servidor:

{
  "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: para facilitarte la vida, puedes usar la cuenta test preexistente que ya está poblada con datos.

Tarea

Comencemos reemplazando la sección "Balance" en el HTML para agregar elementos de marcador de posición:

<section>
  Balance: <span id="balance"></span><span id="currency"></span>
</section>

También agregaremos una nueva sección justo debajo para mostrar la descripción de la cuenta:

<h2 id="description"></h2>

Dado que la descripción de la cuenta funciona como un título para el contenido debajo de ella, está marcada semánticamente como un encabezado. Aprende más sobre cómo la estructura de encabezados es importante para la accesibilidad y analiza críticamente la página para determinar qué más podría ser un encabezado.

A continuación, crearemos una nueva función en app.js para llenar el marcador de posición:

function updateDashboard() {
  if (!account) {
    return navigate('/login');
  }

  updateElement('description', account.description);
  updateElement('balance', account.balance.toFixed(2));
  updateElement('currency', account.currency);
}

Primero, verificamos que tenemos los datos de la cuenta que necesitamos antes de continuar. Luego usamos la función updateElement() que creamos anteriormente para actualizar el HTML.

Para hacer que la visualización del balance sea más atractiva, usamos el método toFixed(2) para forzar la visualización del valor con 2 dígitos después del punto decimal.

Ahora necesitamos llamar a nuestra función updateDashboard() cada vez que se cargue la página del tablero. Si ya terminaste la tarea de la lección 1, esto debería ser sencillo, de lo contrario puedes usar la siguiente implementación.

Agrega este código al final de la función updateRoute():

if (typeof route.init === 'function') {
  route.init();
}

Y actualiza la definición de rutas con:

const routes = {
  '/login': { templateId: 'login' },
  '/dashboard': { templateId: 'dashboard', init: updateDashboard }
};

Con este cambio, cada vez que se muestra la página del tablero, se llama a la función updateDashboard(). Después de un inicio de sesión, deberías poder ver el balance de la cuenta, la moneda y la descripción.

Crear filas de tabla dinámicamente con plantillas HTML

En la primera lección usamos plantillas HTML junto con el método appendChild() para implementar la navegación en nuestra aplicación. Las plantillas también pueden ser más pequeñas y usarse para llenar partes repetitivas de una página de manera dinámica.

Usaremos un enfoque similar para mostrar la lista de transacciones en la tabla HTML.

Tarea

Agrega una nueva plantilla en el <body> del HTML:

<template id="transaction">
  <tr>
    <td></td>
    <td></td>
    <td></td>
  </tr>
</template>

Esta plantilla representa una sola fila de tabla, con las 3 columnas que queremos llenar: fecha, objeto y monto de una transacción.

Luego, agrega esta propiedad id al elemento <tbody> de la tabla dentro de la plantilla del tablero para que sea más fácil de encontrar usando JavaScript:

<tbody id="transactions"></tbody>

Nuestro HTML está listo, pasemos al código JavaScript y creemos una nueva función createTransactionRow:

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;
}

Esta función hace exactamente lo que su nombre implica: usando la plantilla que creamos anteriormente, crea una nueva fila de tabla y llena su contenido usando los datos de la transacción. Usaremos esto en nuestra función updateDashboard() para llenar la tabla:

const transactionsRows = document.createDocumentFragment();
for (const transaction of account.transactions) {
  const transactionRow = createTransactionRow(transaction);
  transactionsRows.appendChild(transactionRow);
}
updateElement('transactions', transactionsRows);

Aquí usamos el método document.createDocumentFragment() que crea un nuevo fragmento del DOM en el que podemos trabajar, antes de finalmente adjuntarlo a nuestra tabla HTML.

Todavía hay una cosa más que debemos hacer antes de que este código funcione, ya que nuestra función updateElement() actualmente solo admite contenido de texto. Cambiemos su código un poco:

function updateElement(id, textOrNode) {
  const element = document.getElementById(id);
  element.textContent = ''; // Removes all children
  element.append(textOrNode);
}

Usamos el método append() ya que permite adjuntar texto o Nodos del DOM a un elemento padre, lo cual es perfecto para todos nuestros casos de uso. Si intentas usar la cuenta test para iniciar sesión, ahora deberías ver una lista de transacciones en el panel de control 🎉.


🚀 Desafío

Trabajen juntos para hacer que la página del panel de control se parezca a una aplicación bancaria real. Si ya has estilizado tu aplicación, intenta usar media queries para crear un diseño responsivo que funcione bien tanto en dispositivos de escritorio como móviles.

Aquí tienes un ejemplo de una página de panel de control estilizada:

Captura de pantalla de un ejemplo del resultado del panel de control después de estilizarlo

Cuestionario posterior a la clase

Cuestionario posterior a la clase

Tarea

Refactoriza y comenta tu código


Descargo de responsabilidad:
Este documento ha sido traducido utilizando el servicio de traducción automática Co-op Translator. Si bien nos esforzamos por lograr precisión, tenga en cuenta que las traducciones automáticas pueden contener errores o imprecisiones. El documento original en su idioma nativo debe considerarse como la fuente autorizada. Para información crítica, se recomienda una traducción profesional realizada por humanos. No nos hacemos responsables de malentendidos o interpretaciones erróneas que puedan surgir del uso de esta traducción.