El día de hoy veremos un algoritmo que he querido desarrollar desde hace mucho y que no fue tan complejo como creí. Se trata de una forma de crear una tabla con JavaScript y que los datos se acomoden automáticamente en cada columna para que no superen una longitud predefinida.
De este modo podremos crear tablas (en forma de texto) en donde el contenido se va a ajustar automáticamente, especificando un separador y una longitud por columna.
Este algoritmo sirve para varias cosas, entre ellas la de imprimir datos tabulados en una impresora térmica ya que los tickets de las impresoras térmicas son un poco reducidos.
Explicación del algoritmo
Con lo único que trabajamos es con cadenas. Debemos cortar o dividir estas cadenas a una máxima longitud. Si la cadena supera cierta longitud entonces debemos pasarla a una nueva línea.
Estamos trabajando con columnas, así que debemos limitar la cadena por cada columna que tengamos. Luego debemos calcular cuál de las columnas tiene la mayor cantidad de filas para ajustar las demás a la máxima “altura”.
Finalmente solo imprimimos y usamos un separador de filas y columnas opcional. Así es como podremos imprimir tablas alineadas en la consola con JS.
Separar cadena en arreglo si supera cierta longitud
Entonces la primera función que veremos es la que divide una cadena en pedazos limitados a una máxima longitud que ya expliqué detenidamente en otro post:
El código queda así:
const separarCadenaEnArregloSiSuperaLongitud = (cadena, maximaLongitud) => {
const resultado = [];
let indice = 0;
while (indice < cadena.length) {
const pedazo = cadena.substring(indice, indice + maximaLongitud);
indice += maximaLongitud;
resultado.push(pedazo);
}
return resultado;
}
Está un poco modificado porque en lugar de concatenar la cadena a un resultado lo que estoy haciendo es agregar la substring al arreglo.
Dividir cadena y contar saltos de línea
La siguiente función se encarga de invocar a la que separa la cadena en arreglos y además realiza un conteo para saber cuál de las columnas tiene la mayor cantidad de líneas.
const dividirCadenasYEncontrarMayorConteoDeBloques = (contenidosConMaximaLongitud) => {
let mayorConteoDeCadenasSeparadas = 0;
const cadenasSeparadas = [];
for (const contenido of contenidosConMaximaLongitud) {
const separadas = separarCadenaEnArregloSiSuperaLongitud(contenido.contenido, contenido.maximaLongitud);
cadenasSeparadas.push({ separadas, maximaLongitud: contenido.maximaLongitud });
if (separadas.length > mayorConteoDeCadenasSeparadas) {
mayorConteoDeCadenasSeparadas = separadas.length;
}
}
return [cadenasSeparadas, mayorConteoDeCadenasSeparadas];
}
Es importante notar que el parámetro que recibe la función es un arreglo de objetos (un objeto por cada columna deseada) en donde cada objeto debe tener la propiedad contenido
(que es la cadena que vamos a dividir) y maximaLongitud
que se refiere como bien lo dice a la máxima longitud que esa cadena podrá tener.
Tabular datos
Finalmente llegamos a la función que permite hacer tablas de texto con JavaScript en el navegador y servidor. Este método usa los dos descritos anteriormente y recibe en orden:
cadenas
: un arreglo de objetos. Cada objeto representa una columna de la tabla y debe tener la propiedadcontenido
ymaximaLongitud
relleno
: el carácter con el que se rellenará la celda cuando la longitud de la cadena sea menor que la máxima longitud (recuerda que al final la tabla estará alineada por eso se necesita rellenar)separadorColumnas
: el separador de columnas que puede ser un pipe (|
), un espacio o lo que tú prefieras.
Ahora veamos la función:
const tabularDatos = (cadenas, relleno, separadorColumnas) => {
const [arreglosDeContenidosConMaximaLongitudSeparadas, mayorConteoDeBloques] = dividirCadenasYEncontrarMayorConteoDeBloques(cadenas)
let indice = 0;
const lineas = [];
while (indice < mayorConteoDeBloques) {
let linea = "";
for (const contenidos of arreglosDeContenidosConMaximaLongitudSeparadas) {
let cadena = "";
if (indice < contenidos.separadas.length) {
cadena = contenidos.separadas[indice];
}
if (cadena.length < contenidos.maximaLongitud) {
cadena = cadena + relleno.repeat(contenidos.maximaLongitud - cadena.length);
}
linea += cadena + separadorColumnas;
}
lineas.push(linea);
indice++;
}
return lineas;
}
Modo de uso
Ya te enseñé todas las funciones y tal vez por ahora no les encuentres el mayor sentido. Veamos lo interesante ahora con un ejemplo de uso.
Quiero imprimir 3 columnas que contienen la descripción del producto, la cantidad y el precio. Obviamente yo no sé cuál será la longitud de cada uno, pero quiero que se imprima correctamente y que si supera la longitud, se ajuste automáticamente.
Fíjate en que el producto puede ocupar máximo 10 caracteres, la cantidad 5 y el subtotal o precio 6. Al invocar la función con los siguientes datos:
const producto = "Nintendo Switch OLED con 2 juegos incluidos (Zelda BOTW y TOTK)";
const cantidad = "1.00";
const subtotal = "$152,986.22";
const longitudProducto = 10;
const longitudCantidad = 5;
const longitudSubtotal = 6;
const separadorColumnas = "|";
const separadorColumnasEnSeparador = "+";
const columnasEncabezado = [{ contenido: "Producto", maximaLongitud: longitudProducto }, { contenido: "Cant.", maximaLongitud: longitudCantidad }, { contenido: "Precio", maximaLongitud: longitudSubtotal },];
const columnasContenido = [{ contenido: producto, maximaLongitud: longitudProducto }, { contenido: cantidad, maximaLongitud: longitudCantidad }, { contenido: subtotal, maximaLongitud: longitudSubtotal },];
const lineasEncabezado = tabularDatos(columnasEncabezado, " ", separadorColumnas);
const lineasSeparador = tabularDatos([{ contenido: "-", maximaLongitud: longitudProducto }, { contenido: "-", maximaLongitud: longitudCantidad }, { contenido: "-", maximaLongitud: longitudSubtotal }], "-", separadorColumnasEnSeparador);
const lineasCuerpo = tabularDatos(columnasContenido, " ", separadorColumnas);
for (const linea of lineasEncabezado) {
console.log(linea);
}
for (const linea of lineasSeparador) {
console.log(linea);
}
for (const linea of lineasCuerpo) {
console.log(linea);
}
Se obtiene el siguiente resultado:
C:\Users\parzibyte\Desktop>node cortar.js
Producto |Cant.|Precio|
----------+-----+------+
Nintendo S|1.00 |$152,9|
witch OLED| |86.22 |
con 2 jue| | |
gos inclui| | |
dos (Zelda| | |
BOTW y TO| | |
TK) | | |
Si cambiamos los parámetros para que por ejemplo el precio y producto ocupen más, tenemos lo siguiente:
C:\Users\parzibyte\Desktop>node cortar.js
Producto |Cant.|Precio |
---------------+-----+----------+
Nintendo Switch|1.00 |$152,986.2|
OLED con 2 jue| |2 |
gos incluidos (| | |
Zelda BOTW y TO| | |
TK) | | |
Incluso podemos dejar todo en una longitud máxima de 2 y seguirá funcionando:
C:\Users\parzibyte\Desktop>node cortar.js
Pr|Ca|Pr|
od|nt|ec|
uc|. |io|
to| | |
--+--+--+
Ni|1.|$1|
nt|00|52|
en| |,9|
do| |86|
S| |.2|
wi| |2 |
tc| | |
h | | |
OL| | |
ED| | |
c| | |
on| | |
2| | |
j| | |
ue| | |
go| | |
s | | |
in| | |
cl| | |
ui| | |
do| | |
s | | |
(Z| | |
el| | |
da| | |
B| | |
OT| | |
W | | |
y | | |
TO| | |
TK| | |
) | | |
Al final tenemos líneas que podemos enviar a cualquier lugar. Si combinamos esto con mi plugin para imprimir en impresoras térmicas desde JavaScript podemos alcanzar un resultado parecido al siguiente:
Lo mejor es que tú puedes personalizar el separador de columnas, la máxima longitud, máximo alto, etcétera. Además, puedes crear tus separadores de filas y darle un ancho máximo a cada columna con la seguridad de que los datos no se van a desordenar.
Por cierto, me inspiré en la salida de MySQL para crear mis separadores, ya que si te fijas cuando se imprimen datos desde la CLI se usan los mismos separadores. Ejemplo:
MariaDB [spos1]> select * from productos;
+------------+--------------+-------------+--------------+-------------+------------+-------+
| idProducto | codigoBarras | descripcion | precioCompra | precioVenta | existencia | stock |
+------------+--------------+-------------+--------------+-------------+------------+-------+
| 1 | 123 | 213 | 23.00 | 3.00 | 311.00 | 23.00 |
+------------+--------------+-------------+--------------+-------------+------------+-------+
1 row in set (0.000 sec)
Te repito que siempre quise hacer esa función justamente para imprimir tablas en los tickets, pero se puede usar para muchas más cosas y modificar lo que sea necesario. Recuerda que funciona en el lado del servidor con Node y también en el navegador web.
Por aquí te dejo más tutoriales de JavaScript en mi blog.