Creación de nodos con JavaScript
Un enfoque práctico inspirado en Negrino y Smith
✍️ Introducción personal: Cómo comencé a manipular el DOM
Por allá por el año 2005, me estaba metiendo en el mundo de la programación web. En ese momento trabajaba con HTML, algo de ASP, PHP y un poco de JavaScript. Creía que con eso bastaba para defenderme. Sin embargo, me despidieron... y reconozco que fue por una falencia mía. No entendía realmente cómo funcionaba el navegador por dentro ni qué era eso del "DOM".
Fue entonces que me topé con dos libros que cambiarían por completo mi forma de ver el desarrollo web: "JavaScript" y "JavaScript and Ajax", de Tom Negrino y Dori Smith, publicados por Peachpit Press, un sello de Prentice Hall/Pearson Education.
A través de sus explicaciones claras y ejemplos directos, entendí que manipular el DOM no era un acto mágico reservado para genios, sino una habilidad lógica, concreta… y sobre todo, poderosa.
Desde entonces, nunca más vi una página web como algo estático. Descubrí que con unas pocas líneas de código era posible crear elementos, responder a acciones del usuario y cambiar el contenido en tiempo real. De hecho, aún conservo ambos libros como recordatorio de ese momento de descubrimiento.
Este tutorial está basado en esa experiencia. Mi intención es que aprendas desde cero cómo manipular el DOM: cómo crear elementos, responder a eventos, y si te animas, incluso cómo traer datos desde el servidor usando Ajax. Todo con JavaScript puro, sin frameworks, para que entiendas qué ocurre debajo del capó.
🖥️ Escenario práctico: pantalla de Punto de Venta (POS)
En este caso, asumiremos que estamos desarrollando una pantalla de ventas tipo POS (Punto de Venta), como las que se usan en supermercados, tiendas o farmacias. En este tipo de aplicaciones es común que el usuario ingrese productos escaneando códigos o escribiendo nombres, y que cada producto se agregue a una tabla con su cantidad, precio unitario y total.
Cada vez que se agrega un producto, debemos crear una nueva fila en la tabla de detalles. Esa fila debe contener varios campos:
-
Código del producto
-
Nombre del producto
-
Cantidad
-
Precio unitario
-
Total (calculado)
-
Un botón para eliminar la fila
Como no sabemos cuántos productos agregará el usuario, no podemos tener esas filas definidas en el HTML desde el inicio. Es por eso que usaremos JavaScript para crear nodos del DOM dinámicamente, es decir, crear elementos HTML desde el script en tiempo real.
A continuación, veremos paso a paso cómo lograrlo usando solo JavaScript puro, sin librerías externas.
Creación dinámica de filas en una tabla para una pantalla POS
En este ejemplo, asumimos que estamos desarrollando una pantalla de Punto de Venta (POS) donde cada vez que se agrega un producto, queremos mostrarlo en una tabla con sus detalles: código, nombre, cantidad, precio y total. Además, cada fila tendrá un botón para eliminarla.
Para ello usaremos JavaScript para crear dinámicamente los nodos HTML que conforman cada fila y sus celdas.
function AgregaDetalle(ProductoCod, ProductoNom, PrecioVenta, Cantidad) {
let precio = parseFloat(PrecioVenta) || 0;
let cantidad = parseInt(Cantidad) || 0;
let total = precio * cantidad;
var auxTotal = total;
// Obtener referencia al tbody de la tabla y el número actual de filas
var tabla = document.getElementById("TablaDetalle").getElementsByTagName('tbody')[0];
var nro_filas = parseInt(document.getElementById("NRO_FILAS").value);
// Crear la fila y asignarle un id único
var FilaTabla = document.createElement("tr");
FilaTabla.id = "FilaTabla" + nro_filas;
// Crear las celdas
var Columna0 = document.createElement("td");
var Columna1 = document.createElement("td");
var Columna2 = document.createElement("td");
var Columna3 = document.createElement("td");
var Columna4 = document.createElement("td");
// Crear los inputs que contendrán los datos
var Input0 = document.createElement("input");
var Input1 = document.createElement("input");
var Input2 = document.createElement("input");
var Input3 = document.createElement("input");
var Input4 = document.createElement("input");
var Input5 = document.createElement("input");
// Configurar atributos de los inputs
Input0.setAttribute('readonly', 'yes');
Input0.setAttribute('maxlength', '8');
Input0.setAttribute('size', '5');
Input1.setAttribute('size', '15');
Input1.setAttribute('readonly', 'yes');
Input2.setAttribute('size', '8');
Input2.setAttribute('readonly', 'yes');
Input3.setAttribute('readonly', 'yes');
Input4.setAttribute('readonly', 'yes');
Input0.setAttribute('type', 'hidden');
Input5.setAttribute('type', 'hidden');
Input4.setAttribute('size', '8');
Input3.setAttribute('size', '8');
// Asignar nombre y id con índice para cada input
Input0.setAttribute('name', 'ProductoCod' + nro_filas);
Input1.setAttribute('name', 'ProductoNom' + nro_filas);
Input3.setAttribute('name', 'ProductoPre' + nro_filas);
Input2.setAttribute('name', 'Cantidad' + nro_filas);
Input4.setAttribute('name', 'Total' + nro_filas);
Input5.setAttribute('name', 'Porcentaje' + nro_filas);
Input0.setAttribute('id', 'ProductoCod' + nro_filas);
Input1.setAttribute('id', 'ProductoNom' + nro_filas);
Input3.setAttribute('id', 'ProductoPre' + nro_filas);
Input2.setAttribute('id', 'Cantidad' + nro_filas);
Input4.setAttribute('id', 'Total' + nro_filas);
Input5.setAttribute('id', 'Porcentaje' + nro_filas);
// Asignar valores recibidos a los inputs
Input0.value = ProductoCod;
Input1.value = ProductoNom;
Input2.value = Cantidad;
Input3.value = PrecioVenta;
Input4.value = auxTotal;
Input5.value = 0;
// Agregar inputs a las celdas correspondientes
Columna0.appendChild(Input0);
Columna0.appendChild(Input5);
Columna0.appendChild(Input1);
Columna1.appendChild(Input2);
Columna2.appendChild(Input3);
Columna3.appendChild(Input4);
// Crear botón para eliminar la fila
var BtnEliminar = document.createElement("button");
BtnEliminar.textContent = "Eliminar";
BtnEliminar.className = "btn btn-danger"; // Clases Bootstrap para estilizar
BtnEliminar.type = "button";
BtnEliminar.setAttribute('id', 'BtnEliminar' + nro_filas);
// Añadir evento para eliminar fila al hacer clic
let idFila = "FilaTabla" + nro_filas;
BtnEliminar.addEventListener("click", function () {
EliminaFila(idFila);
});
Columna4.appendChild(BtnEliminar);
// Agregar las celdas a la fila
FilaTabla.appendChild(Columna0);
FilaTabla.appendChild(Columna1);
FilaTabla.appendChild(Columna2);
FilaTabla.appendChild(Columna3);
FilaTabla.appendChild(Columna4);
// Finalmente, agregar la fila al tbody de la tabla
tabla.appendChild(FilaTabla);
// Incrementar contador de filas para el próximo elemento
document.getElementById("NRO_FILAS").value = nro_filas + 1;
}
Explicación paso a paso
-
Cálculo de total: Convertimos precio y cantidad a números y calculamos el total de esa fila.
-
Referencias: Obtenemos el
<tbody>
donde insertaremos la nueva fila y leemos el contador de filas actuales para generar IDs únicos. -
Crear la fila (
<tr>
) y las celdas (<td>
). -
Crear inputs ocultos y visibles para almacenar los datos del producto y mostrarlos (muchos son
readonly
para evitar que el usuario los modifique directamente). -
Configurar atributos como
name
,id
,size
,readonly
, y valores de cada input. -
Agregar inputs a las celdas correspondientes.
-
Crear botón "Eliminar" con clase Bootstrap para estilos, y agregar un event listener para borrar la fila.
-
Añadir todas las celdas a la fila y esta al
<tbody>
. -
Actualizar contador de filas para futuras filas nuevas.
🧽 Eliminar una fila y reordenar los índices
Cuando eliminamos una fila de la tabla, debemos tener en cuenta que todos los elementos HTML (inputs, botones, etc.) están numerados con un sufijo que representa su posición: ProductoNom0
, ProductoNom1
, etc.
Si no reindexamos los elementos restantes, podríamos tener problemas para:
-
Procesar el formulario (los datos quedan con huecos).
-
Crear nuevas filas (colisiones de IDs).
-
Usar eventos (el botón “Eliminar” seguiría apuntando al ID anterior).
Aquí tienes la función que soluciona ese problema:
function EliminaFila(auxIdFila) {
var auxNroFilas = parseInt(document.getElementById('NRO_FILAS').value);
var i = 0;
var j = 0;
for (i = 0; i <= auxNroFilas - 1; i++) {
// Obtener referencias
var FilaTabla = document.getElementById('FilaTabla' + i);
var row = document.getElementById(FilaTabla.id);
var Columna0 = document.getElementById('Columna0' + i);
var Columna1 = document.getElementById('Columna1' + i);
var Columna2 = document.getElementById('Columna2' + i);
var Columna3 = document.getElementById('Columna3' + i);
var Columna4 = document.getElementById('Columna4' + i);
var Input0 = document.getElementById('ProductoCod' + i);
var Input1 = document.getElementById('ProductoNom' + i);
var Input2 = document.getElementById('ProductoPre' + i);
var Input3 = document.getElementById('Cantidad' + i);
var Input4 = document.getElementById('Total' + i);
var Input5 = document.getElementById('Porcentaje' + i);
var BtnEliminar = document.getElementById('BtnEliminar' + i);
// Si esta no es la fila a eliminar, reordenar
if (FilaTabla.id !== auxIdFila) {
// Actualizar names
Input0.name = 'ProductoCod' + j;
Input1.name = 'ProductoNom' + j;
Input2.name = 'ProductoPre' + j;
Input3.name = 'Cantidad' + j;
Input4.name = 'Total' + j;
Input5.name = 'Porcentaje' + j;
// Actualizar IDs de columnas
Columna0.id = 'Columna0' + j;
Columna1.id = 'Columna1' + j;
Columna2.id = 'Columna2' + j;
Columna3.id = 'Columna3' + j;
Columna4.id = 'Columna4' + j;
// Actualizar IDs de inputs
Input0.id = 'ProductoCod' + j;
Input1.id = 'ProductoNom' + j;
Input2.id = 'ProductoPre' + j;
Input3.id = 'Cantidad' + j;
Input4.id = 'Total' + j;
Input5.id = 'Porcentaje' + j;
// Actualizar ID de fila
FilaTabla.id = 'FilaTabla' + j;
// Reasignar evento de eliminación con el nuevo índice
if (BtnEliminar && BtnEliminar instanceof HTMLButtonElement) {
BtnEliminar.id = 'BtnEliminar' + j;
BtnEliminar.onclick = null; // Limpieza
let idFila = 'FilaTabla' + j;
BtnEliminar.onclick = function () {
EliminaFila(idFila);
};
}
j++; // Solo avanza si no se eliminó
} else {
// Eliminar la fila del DOM
row.parentNode.removeChild(row);
document.getElementById("NRO_FILAS").value = auxNroFilas - 1;
}
}
// Opcional: recalcular totales u otras actualizaciones visuales
// CalculoTotal();
}
✅ ¿Qué hace esta función?
-
Busca todas las filas activas.
-
Si la fila no es la que debe eliminar, la renumera (reindexa).
-
Si es la fila seleccionada, la elimina del DOM.
-
Reasigna los eventos a los botones
Eliminar
con sus nuevos índices. -
Actualiza el contador general
NRO_FILAS
.
💡 Tip:
Este patrón es útil siempre que tus datos dependan del índice numérico. Si usaras identificadores únicos (UUID), podrías evitar reindexar y simplemente eliminar la fila. Pero cuando trabajas con formularios tradicionales o envías datos por POST
, mantener el orden es importante.
Comentarios
Publicar un comentario