Documentation Index
Fetch the complete documentation index at: https://mintlify.com/midudev/jscamp/llms.txt
Use this file to discover all available pages before exploring further.
En este módulo tomamos la maquetación estática del módulo anterior y la convertimos en una aplicación interactiva usando JavaScript Vanilla. Sin frameworks, sin librerías externas: aprenderemos a manipular el DOM directamente, cargar datos desde un archivo JSON mediante la Fetch API, implementar filtros dinámicos y crear un Web Component personalizado para los avatares de usuario. Todo el código se organiza en módulos ES nativos del navegador.
Lo que aprenderás
- Manipulación del DOM: creación dinámica de elementos, actualización de contenido y atributos
data-*.
- Fetch API: carga asíncrona de datos con
fetch(), manejo de Promises y transformación de respuestas JSON.
- Módulos ES: organización del código con
import/export nativo del navegador.
- Web Components personalizados: creación de un custom element con Shadow DOM usando la API
customElements.
- Filtros dinámicos: escucha de eventos en
<select> para filtrar tarjetas de trabajo en tiempo real.
- Carga de datos JSON: uso de un archivo
data.json local como fuente de datos del listado de empleos.
Estructura del Módulo
01-javascript/
├── index.html # Página de inicio (hero + buscador)
├── empleos.html # Página de listado de empleos con filtros
├── styles.css # Estilos del proyecto
├── main.js # Punto de entrada; importa todos los módulos
├── config.js # Estado global compartido (objeto `state`)
├── fetch-data.js # Carga data.json y renderiza las tarjetas de empleo
├── filters.js # Lógica de filtrado por modalidad usando `<select>`
├── apply-button.js # Manejo del botón "Aplicar" en cada tarjeta
├── devjobs-avatar-element.js # Web Component `<devjobs-avatar>` con Shadow DOM
├── data.json # Datos de empleos de ejemplo en formato JSON
└── background.webp # Imagen hero optimizada
Cargando Datos
El archivo fetch-data.js usa fetch() para cargar data.json de forma asíncrona. Por cada trabajo recibido, crea un elemento <article> con el contenido del empleo y lo añade al contenedor .jobs-listings. Los atributos data-* almacenan los metadatos de filtrado directamente en el DOM.
const container = document.querySelector('.jobs-listings')
const RESULTS_PER_PAGE = 3
fetch("./data.json") /* fetch es asíncrono */
.then((response) => {
return response.json();
})
.then((jobs) => {
jobs.forEach(job => {
const article = document.createElement('article')
article.className = 'job-listing-card'
// Datos de filtrado almacenados como atributos data-*
article.dataset.modalidad = job.data.modalidad
article.dataset.nivel = job.data.nivel
article.dataset.technology = job.data.technology
article.innerHTML = `
<div>
<h3>${job.titulo}</h3>
<small>${job.empresa} | ${job.ubicacion}</small>
<p>${job.descripcion}</p>
</div>
<button class="button-apply-job">Aplicar</button>
`
container.appendChild(article)
})
});
Los atributos data-modalidad, data-nivel y data-technology se añaden a cada <article> para que los filtros puedan leer esos valores sin necesidad de mantener un array en memoria.
Filtros Dinámicos
El archivo filters.js escucha el evento change en el <select> de ubicación/modalidad. Cuando el usuario elige una opción, recorre todos los artículos del DOM, compara el atributo data-modalidad de cada uno con el valor seleccionado y aplica o elimina la clase is-hidden usando classList.toggle.
import { state } from './config.js'
state.count++
console.log(state)
const filter = document.querySelector('#filter-location')
const mensaje = document.querySelector('#filter-selected-value')
filter.addEventListener('change', function () {
const jobs = document.querySelectorAll('.job-listing-card')
const selectedValue = filter.value
if (selectedValue) {
mensaje.textContent = `Has seleccionado: ${selectedValue}`
} else {
mensaje.textContent = ''
}
jobs.forEach(job => {
// Lectura del atributo data-modalidad del elemento
const modalidad = job.getAttribute('data-modalidad')
const isShown = selectedValue === '' || selectedValue === modalidad
job.classList.toggle('is-hidden', isShown === false)
})
})
El módulo también importa el objeto state compartido desde config.js, que actúa como un contador global de módulos cargados:
// config.js
export let state = {
count: 0
}
Web Component: DevJobsAvatar
El archivo devjobs-avatar-element.js define un Web Component personalizado llamado <devjobs-avatar>. Extiende HTMLElement, crea un Shadow DOM aislado y renderiza un <img> que obtiene el avatar del usuario desde el servicio unavatar.io. Los atributos service, username y size son totalmente configurables desde HTML.
class DevJobsAvatar extends HTMLElement {
constructor() {
super(); // llamar al constructor de HTMLElement
this.attachShadow({ mode: 'open' })
}
createUrl(service, username) {
return `https://unavatar.io/${service}/${username}`
}
render() {
const service = this.getAttribute('service') ?? 'github'
const username = this.getAttribute('username') ?? 'midudev'
const size = this.getAttribute('size') ?? '40'
const url = this.createUrl(service, username)
this.shadowRoot.innerHTML = `
<style>
img {
width: ${size}px;
height: ${size}px;
border-radius: 9999px;
}
</style>
<img
src="${url}"
alt="Avatar de ${username}"
class="avatar"
/>
`
}
connectedCallback() {
this.render()
}
}
customElements.define('devjobs-avatar', DevJobsAvatar)
Una vez registrado, puedes usar el elemento en cualquier HTML así:
<devjobs-avatar service="github" username="midudev" size="64"></devjobs-avatar>
El Shadow DOM (attachShadow({ mode: 'open' })) encapsula los estilos del componente, por lo que las reglas CSS del <img> dentro del shadow root no afectan a ningún otro <img> de la página.
Cómo Ejecutar
Navega a la carpeta del módulo
Abre index.html con Live Preview
En Visual Studio Code, haz clic derecho sobre index.html y selecciona “Show Preview”. También puedes abrir empleos.html directamente para ver el listado de empleos con los filtros.Este módulo usa módulos ES (type="module" en los <script>), por lo que no funcionará si abres el archivo directamente con el protocolo file://. Necesitas un servidor local, como el que proporciona la extensión Live Preview de VS Code.
Interactúa con los filtros
En la página empleos.html verás el listado de empleos cargado desde data.json. Usa el selector de modalidad para filtrar las tarjetas en tiempo real. Observa en la consola del navegador cómo el objeto state registra cada módulo cargado.