En el primera mitad de este artículo, configuramos una pila de desarrollo web y creamos una aplicación de ejemplo simple usando Bun, HTMX, Elysia y MongoDB. Aquí, continuaremos explorando nuestra nueva pila mientras limpiamos y abstraemos la capa de acceso a datos de la aplicación de ejemplo y agregamos interacciones HTMX más complejas. También agregaremos otro componente a la pila tecnológica: Pug, un popular motor de plantillas JavaScript que funciona bien con HTMX y ayuda a configurar las interacciones DOM.
La aplicación de ejemplo
Nuestra aplicación de ejemplo actualmente consta de un formulario y una tabla. El formulario permite a los usuarios ingresar citas junto con sus autores, que luego pueden buscarse y mostrarse mediante la interfaz de usuario de la aplicación. Agregué un poco de CSS a la interfaz para que parezca más moderna que lo que dejamos en la Parte 1:
Aquí está el código de interfaz para la interfaz actualizada:
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
<form hx-post="/add-quote" hx-swap-oop="beforeend:#data-list" hx-trigger="every time">
<input type="text" name="quote" placeholder="Enter quote">
<input type="text" name="author" placeholder="Enter author">
<button type="submit">Add Quote</button>
</form>
<ul id="data-list"></ul>
<button hx-get="/quotes" hx-target="#data-list">Load Data</button>
Estamos usando HTMX para impulsar el proceso de envío del formulario y carga de datos en la tabla. También limpié el back-end de la aplicación para que la conectividad de la base de datos ahora sea compartida. Aquí está esa porción de src/index.ts
:
import { Elysia } from "elysia";
import { staticPlugin } from '@elysiajs/static';
const { MongoClient } = require('mongodb');
// Database connection details
const url = "mongodb://127.0.0.1:27017/quote?directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+1.8.0";
const dbName = "quote";
const collectionName = "quotes";
let client = new MongoClient(url, { useUnifiedTopology: true });
// Connect to the database (called only once)
async function connectToDatabase() {
try {
await client.connect();
} catch (error) {
console.error(error);
throw error; // Re-throw the error to indicate connection failure
}
return { client, collection: client.db(dbName).collection(collectionName) };
}
// Close the database connection
async function closeDatabaseConnection(client) {
await client.close();
}
Lo que estamos haciendo aquí es definir la URL de la base de datos como la dirección de host local predeterminada de MongoDB, junto con una base de datos y un nombre de colección. Luego, utilizamos un async
función, connectToDatabase()
, para conectar el cliente y devolverlo conectado a la colección. Nuestro código puede entonces llamar a este método cada vez que necesite acceder a la base de datos, y cuando esté hecho, puede llamar client.close()
.
Usando la conexión de base de datos
Veamos cómo los puntos finales de nuestro servidor utilizarán este soporte de base de datos. Por brevedad, sólo estoy mostrando el /quotes
punto final que controla la tabla:
// Close the database connection
async function closeDatabaseConnection(client) {
await client.close();
}
async function getAllQuotes(collection) {
try {
const quotes = await collection.find().toArray();
// Build the HTML table structure
let html="<table border="1">";
html += '<tr><th>Quote</th><th>Author</th></tr>';
for (const quote of quotes) {
html += `<tr><td>${quote.quote}</td><td>${quote.author}</td></tr>`;
}
html += '</table>';
return html;
} catch (error) {
console.error("Error fetching quotes", error);
throw error; // Re-throw the error for proper handling
}
}
// Main application logic
const app = new Elysia()
.get("https://www.infoworld.com/", () => "Hello Elysia")
.get("/quotes", async () => {
try {
const { client, collection } = await connectToDatabase();
const quotes = await getAllQuotes(collection);
await closeDatabaseConnection(client);
return quotes;
} catch (error) {
console.error(error);
return "Error fetching quotes";
}
})
.use(staticPlugin())
.listen(3000);
console.log(
` Elysia is running at ${app.server?.hostname}:${app.server?.port}`
);
Esto nos da un back-end /quotes
GET punto final al que podemos llamar para obtener los datos de las cotizaciones. El punto final llama al getAllQuotes()
método, que utiliza la colección de connectToDatabase()
para obtener la variedad de citas y autores. Luego genera el HTMX para las filas.
Finalmente, enviamos una respuesta que contiene las filas como HTMX y las filas se insertan en la tabla.
Agregue el motor de plantillas Pug
Crear manualmente la fila HTMX puede provocar frustración y errores. Un motor de plantillas nos permite definir la estructura HTMX en un archivo eterno con una sintaxis limpia.
El motor de plantillas HTML más popular para JavaScript es Pug. Usarlo hará que la creación de vistas en el servidor sea mucho más fácil y escalable que insertarlas en el código JavaScript. La idea básica es tomar nuestros objetos de datos y pasarlos a la plantilla, que aplica los datos y genera HTML. La diferencia aquí es que estamos generando HTML en lugar de HTML. Podemos hacer esto porque HTMLX es esencialmente HTML con extensiones.
Para comenzar, agregue la biblioteca Pug al proyecto con: $ bun add pug
.
Cuando esto se complete, cree un nuevo directorio en la raíz del proyecto llamado /views: ($ mkdir views)
luego agregue un nuevo archivo llamado quotes.pug
:
doctype html
h1 Quotes
table
thead
tr
th Quote
th Author
th Actions
tbody
each quote in quotes
tr(id=`quote-${quote._id}`)
td #{quote.quote}
td #{quote.author}
td
button(hx-delete=`/quotes/${quote._id}` hx-trigger="click" hx-swap="closest tr" hx-confirm="Are you sure?") Delete
#{quote._id}
Pug usa sangría para manejar elementos anidados. Los atributos se mantienen entre paréntesis. Texto sin formato como la palabra. Borrar se proporciona tal cual. Todo esto nos brinda una forma compacta de describir HTML y/o HTMX. Ver el página de inicio de pug para aprender más sobre su sintaxis.
Observe que dentro de una cadena, necesitamos usar ${}
. El #{}
La sintaxis le permite hacer referencia a cualquier objeto de datos que se haya inyectado en la plantilla. Esto es similar a la interpolación de tokens en un marco como Reaccionar. La idea básica es definir la estructura HTML/HTMX general y luego proporcionar variables a la plantilla a las que se hace referencia con #{}
y ${}
.
Proporcionamos las variables nuevamente en el servidor. /quotes
punto final, que utiliza getAllQuotes()
:
import pug from 'pug';
//...
async function getAllQuotes(collection) {
try {
const quotes = await collection.find().toArray();
// Render the Pug template with the fetched quotes
const html = pug.compileFile('views/quotes.pug')({ quotes });
return html;
} catch (error) {
console.error("Error fetching quotes", error);
throw error; // Re-throw the error for proper handling
}
}
Entonces, obtenemos las citas de la base de datos, luego compilamos la plantilla de Pug y pasamos las citas. Luego, Pug hace el trabajo de unir el HTML y los datos. El flujo general es:
- La solicitud llega a
GET /quotes
. - Las cotizaciones se obtienen de MongoDB.
- La plantilla Pug recibe las cotizaciones.
- La plantilla Pug representa las citas como HTML y/o HTMX.
- El HTML y/o HTMX completado se envía como respuesta.
La pantalla resultante se parece a esta:
Interacciones DOM: eliminar una fila
Ahora necesitamos que nuestro botón Eliminar funcione. Simplemente emitiendo un delete
Solicitarlo y manejarlo en el servidor y la base de datos es fácil de hacer con lo que ya hemos visto, pero ¿qué pasa con la actualización de la tabla para reflejar el cambio?
Hay varias formas de abordar la actualización. Podríamos simplemente actualizar toda la tabla o podríamos usar JavaScript o HTMX para eliminar la fila de la tabla. Idealmente, nos gustaría utilizar la última opción y mantener todo como HTMX.
En nuestro views/quotes.pug
plantilla, podemos usar HTMX puro para eliminar la fila:
tbody(hx-target="closest tr" hx-swap="outerHTML")
each quote in quotes
tr(id=`quote-${quote._id}`)
td #{quote.quote}
td #{quote.author}
td
button(hx-delete=`/quotes/${quote._id}` hx-trigger="click" hx-confirm="Are you sure?") Delete
Las partes esenciales aquí son las hx-target=”closest tr”
y hx-swap=”outerHTML”
sobre el tbody
. (El hx-confirm
le permite proporcionar un confirm
diálogo.) El hx-target
dice reemplazar el más cercano tr
al elemento disparador (el botón) con la respuesta. El outHTML
en hx-swap
garantiza que eliminemos todo el elemento de la fila de la tabla, no solo su contenido. En el lado del servidor, devolvemos un éxito (HTTP 200) con un cuerpo vacío, por lo que HTMX simplemente eliminará la fila:
async function deleteQuote(collection, quoteId) {
try {
const result = await collection.deleteOne({ _id: new ObjectId(quoteId) });
if (result.deletedCount === 1) {
return "";
} else {
throw new Error( "Quote not found");
}
} catch (error) {
console.error("Error deleting quote", error);
throw error; // Re-throw the error for proper handling
}
}
Aquí, apenas estamos comenzando a involucrarnos en interacciones DOM más involucradas. HTMX también puede agregar efectos de transición simples a los intercambios en un escenario de eliminación de filas como el nuestro. Puedes ver un ejemplo. en la página de inicio de HTMX.
Conclusión
Aunque este tutorial de dos partes incorpora tecnologías más nuevas como Bun y Elysia, el componente más notable es HTMX. Realmente cambia la forma en que funciona una aplicación en comparación con las API JSON convencionales.
Cuando se combina con un motor de plantillas como Pug y una base de datos como MongoDB, el trabajo de generar UI y manejar solicitudes es sencillo. A medida que una aplicación crece en tamaño, Pug presenta funciones como herencia de plantilla también viene bien.
Para interacciones DOM, HTMX ofrece una funcionalidad flexible lista para usar a través de hx-swap
y hx-target
. Para casos de uso más complicados, siempre puedes recurrir a JavaScript.
En general, toda esta pila funciona bien en conjunto. También puede apreciar la velocidad de Bun cada vez que necesite ingresar a la línea de comando para hacer algo como agregar una dependencia.
Puedes encontrar el código de este tutorial en mi repositorio de GitHub.
Copyright © 2024 IDG Communications, Inc.