IronWoods.es

Desarrollo web

Blog / Javascript / Añadir elementos a la página

Se pueden "añadir" elementos a una página, bien sea mostrando elementos ocultos (no se añaden, se juega con su visibilidad), o creándolos y añadiéndolo en lugares específicos del DOM, pudiendo añadir un número indeterminado de los mismos.

El método básico para crear elementos desde 0

Se usa una sucesión de instrucciones para crear el nuevo nodo, finalmente se selecciona un contenedor o elemento padre y se añade al mismo.

Ejemplo: crear etiquetas

Se dispondrá un campo de entrada de texto y un botón para ir añadiendo palabras a una "caja".

HTML:


    <main> class="w-full max-w-md bg-white rounded-xl shadow-md p-6 space-y-6">
        <header>
            <h1 class="text-xl font-bold text-slate-800">
                Ejemplo Vanilla JS: Añadir Palabras
            </h1>
        </header>

        
        <div class="flex gap-2">
            <input type="text"
                name="word"
                placeholder="Escribe una palabra..."
                class="flex-1 px-3 py-2 border border-slate-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500">
            <button type="button"
                data-action="add-word"
                class="px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white text-sm font-medium rounded-lg transition-colors">
                Añadir palabra
            </button>
        </div>

        
        <div data-ref="words-container"
            class="flex flex-wrap gap-2 p-2 border border-dashed border-slate-400 rounded-lg">
        </div>
    </main>

Se requieren 2 bloques de JS, el primero define las variables con los nodos HTML implicados, captura la pulsación del botón y lanza la ejecución del script con la lógica: obtiene el texto del input y lo limpia, crea un nodo y lo sitúa dentro del nodo de un elemento HTML preexistente (contenedor).


    const addButton = document.querySelector('button[data-action="add-word"]');
    const wordInput = document.querySelector('input[name="word"]');
    const wordsContainer = document.querySelector('div[data-ref="words-container"]');

    // Handle the button click event
    addButton.addEventListener('click', function() {
        const word = wordInput.value.trim();
        if (word === '') {
            alert('No has introducido una palabra');
            return;
        }
        wordInput.value = ''; // Clean the input

        addWordToList(word);
    });

    // Create the word HTML element and add to the words list container
    // I use Tailwind classes to style the elements
    function addWordToList(word) // void
    {
        const item = document.createElement('div');
        item.className = 'text-sm text-gray-800 p-2 bg-white rounded shadow-sm';
        item.textContent = word;

        wordsContainer.append(item);
    }

Fichero completo

Esto es muy sencillo, no es lo habitual. Algo más funcional, ampliando el script: añadimos un pequeño botón a cada palabra que permite eliminarla de la lista. Reemplazaré solo la función addWordToList() en este ejemplo:


    // Create the word HTML element and add to the words list container
    // I use Tailwind classes to style the elements
    function addWordToList(word) // void
    {
        const item = document.createElement('div');
        const removeButton = composeRemoveButton(item);
        const span = composeWordTag(word);

        item.className = 'flex items-center justify-between p-2 bg-white rounded shadow-sm';

        item.append(span, removeButton);
        wordsContainer.append(item);
    }

    // aux function ////////////////////////////////////////////////

    function composeRemoveButton(item) // js node
    {
        const button = document.createElement('button');
        button.className = 'text-red-600 hover:text-red-800 text-sm font-bold px-2';
        button.setAttribute('aria-label', 'Eliminar de la lista');
        button.setAttribute('data-action', 'delete-word');
        button.textContent = '×';
        button.type = 'button';

        button.addEventListener('click', () => {
            item.remove();
        });

        return button;
    }
    function composeWordTag(word) // js node
    {
        const span = document.createElement('span');
        span.textContent = word;
        span.className = 'text-sm text-gray-800';

        return span;
    }

Hay elementos en una página que puedes ser muy complejos, crearlos con este método no es práctico.

Fichero completo

Método alternativo para crear elementos desde 0

Cómo la sucesión de instrucciones para crear nodos, es bastante repetitiva, pueden encapsularse en funciones para trabajar de forma más limpia y predecible.

Hace años escribí una clase a la que llamé NodeBuilder para crear y añadir elementos al DOM. Esta clase gestiona internamente como se crean los nodos y permite trabajar siempre de la misma forma reduciendo la posibilidad de errores.

NodeBuilder dispone del método build() que acepta cuatro parámetros:

  • El elemento HTML a crear: 'p', 'li', 'button', input, etc.
  • Atributos del elemento HTML: 'class', 'id', data-attrs, 'title', etc.
  • Un array con nodos hijos. Podemos pasarlo vacío o null sino vamos a añadir hijos.
  • Texto contenido por el elemento.

Por supuesto, los 2 últimos parámetros se omiten si el elemento a crear no tiene ni hijos ni texto, p.e. un input.

Ejemplo: crear etiquetas

Voy a reescribir el script del ejemplo anterior usando mi "creador", sólo incluiré aquí el JS necesario, no la parte del HTML que no cambia, salvo porque importa un fichero con el NodeBuilder en su head


    const addButton = document.querySelector('button[data-action="add-word"]');
    const wordInput = document.querySelector('input[name="word"]');
    const wordsContainer = document.querySelector('div[data-ref="words-container"]');

    // Handle the button click event
    addButton.addEventListener('click', function () {
        const word = wordInput.value.trim();
        if (word === '') {
            alert('No has introducido una palabra');
            return;
        }
        wordInput.value = ''; // Clean the input

        addWordToList(word);
    });

    // Create the word HTML element and add to the words list container
    // I will to use Tailwind classes to style the elements
    function addWordToList(word) // void
    {
        const item = nodeBuilder.build(
            'div',
            { class: 'flex items-center justify-between p-2 bg-white rounded shadow-sm' }
        );
        const removeButton = composeRemoveButton(item);
        const span = composeWordTag(word);
        item.append(span, removeButton);

        wordsContainer.append(item);
    }

    // aux function ////////////////////////////////////////////////

    function composeRemoveButton(item) // js node
    {
        const attributes = {
            type: 'button',
            class: 'text-red-600 hover:text-red-800 text-sm font-bold px-2',
            'aria-label': 'Eliminar de la lista',
            'data-action': 'delete-word',
        };
        const button = nodeBuilder.build(
            'button',
            attributes,
            null,
            'x'
        );

        button.addEventListener('click', () => {
            item.remove();
        });

        return button;
    }
    function composeWordTag(word) // js node
    {
        const attributes = {
            class: 'text-sm text-gray-800',
        };
        const span = nodeBuilder.build(
            'button',
            attributes,
            null,
            word
        );

        return span;
    }

La reutilización de bloques de instrucciones comunes encapsulándolas en funciones simplifica mucho los scripts y mejora legibilidad y mantenibilidad.

Fichero completo

El método pro: usar templates

Hay un método que evita tener que construir y añadir nodos complejos y permite tener, oculto dentro de la página, el HTML del elemento que se va a añadir, usando la etiqueta HTML template, que permite copiarlo, modificarlo y añadirlo a su lugar.

Usar templates nos permite además poder ver el HTML de la página, entender que elementos la componen y predecir como va a funcionar, sin tener que ejecutar scripts y observar su funcionamiento.

El navegador ignora el contenido de template al renderizar la página, por lo que sirve perfectamente como molde para añadir elementos de forma repetitiva al DOM.

Ejemplo: crear etiquetas usando templates

Se dispondrá un campo de entrada de texto y un botón para ir añadiendo palabras a una "caja".

Como el HTML de los elementos que añadiremos a la página se integran en la misma, dentro de template, los scripts se simplifican, y no necesitaría usar el "builder" o construir nodos desde 0.


    <main> class="w-full max-w-md bg-white rounded-xl shadow-md p-6 space-y-6">
        <header>
            <h1 class="text-xl font-bold text-slate-800">
                Ejemplo Vanilla JS: Añadir Palabras
            </h1>
        </header>

        
        <div class="flex gap-2">
            <input type="text"
                name="word"
                placeholder="Escribe una palabra..."
                class="flex-1 px-3 py-2 border border-slate-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500">
            <button type="button"
                data-action="add-word"
                class="px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white text-sm font-medium rounded-lg transition-colors">
                Añadir palabra
            </button>
        </div>

        
        <div data-ref="words-container"
            class="flex flex-wrap gap-2 p-2 border border-dashed border-slate-400 rounded-lg">
        </div>
            <template data-ref="word-template">
            <div class="flex items-center justify-between p-2 bg-white rounded shadow-sm">
                <span class="text-sm text-gray-800"></span>
                <button type="button"
                    class="text-red-600 hover:text-red-800 text-sm font-bold px-2"
                    aria-label="Eliminar de la lista"
                    data-action="delete-word">
                    ×
                </button>
            </div>
        </template>
    </main>


    <script>
        const addButton = document.querySelector('button[data-action="add-word"]');
        const template = document.querySelector('template[data-ref="word-template"]');
        const wordInput = document.querySelector('input[name="word"]');
        const wordsContainer = document.querySelector('div[data-ref="words-container"]');

        // Handle the button click event
        addButton.addEventListener('click', function () {
            const word = wordInput.value.trim();
            if (word === '') {
                alert('No has introducido una palabra');
                return;
            }
            wordInput.value = ''; // Clean the input

            addWordToList(word);
        });

        // Use event delegation to remove words from the container
        wordsContainer.addEventListener('click', (event) => {
            const deleteButton = event.target.closest('[data-action="delete-word"]');
            if (!deleteButton) {
                return;
            }

            const wordItem = deleteButton.closest('div.flex');
            if (wordItem) {
                wordItem.remove();
            }
        });

        // Create the word HTML element and add to the words list container
        // I use Tailwind classes to style the elements
        function addWordToList(word) // void
        {
            const item = template.content.cloneNode(true);
            item.querySelector('span').textContent = word;

            wordsContainer.append(item);
        }
    </script>

Hay un pequeño cambio en cómo se gestionan los eventos de los elementos internos del "clon", pero el código en general resulta mucho más simple y más cuando los nodos que vamos a añadir crecen en complejidad.

Fichero completo