Introducción
Fieldnote es una pequeña guía de actividades de fin de semana. Un enlace de búsqueda útil debe describir la vista: palabra clave, categoría, orden y página de resultados. Una persona que abra ese enlace debe ver los mismos eventos, incluidas la hora y el lugar correctos de dos eventos con el mismo título. Construirás esa relación de estado sobre un sitio adaptable ya preparado y un pequeño catálogo local.
Necesitas conocimientos básicos de rutas en la terminal, formularios HTML, diseño y foco con CSS, y arrays, objetos, funciones, selección del DOM y eventos de JavaScript. Este laboratorio presenta el filtrado de arrays, comparadores, límites de paginación, URL y URLSearchParams. La búsqueda y el análisis de URL tienen muchas aplicaciones; aquí el resultado es una vista completa y reproducible de cuatro valores. El renderizador preparado crea las tarjetas a partir de los registros que selecciona tu código. No necesitas conocimientos de backend ni de frameworks. Todos los eventos son sintéticos. Los 55 minutos son una estimación del autor que incluye la lectura y los errores habituales.
Usa Workspace para los comandos y Fieldnote para la página de entrada. Este laboratorio se inicia de forma independiente. Edita únicamente /home/labex/project/fieldnote/work/selection.js, state.js y url.js; app.js es el controlador preparado en este laboratorio y será tu trabajo en el siguiente. Conserva catalog.js, view.js, index.html, style.css, la vista Study independiente y el servicio preparado. El controlador ya envía formularios y vuelve a dibujar la página, utilizando replaceState para actualizar la entrada actual. El historial del navegador es aquí un comportamiento preparado, todavía no un requisito de aprendizaje. Las etiquetas del sitio permanecen en inglés.
Seleccionar registros reales en un orden estable
En este paso, abrirás una vista compartida que funciona y darás a tu página de trabajo un comportamiento predecible de palabra clave, categoría y orden.
En Fieldnote, elige Open Study view in a new page. Este enlace real abre una página independiente del navegador, fuera de la pestaña integrada del producto. Usa esa nueva página para las acciones del sitio. Su dirección contiene q, category, sort y page; la palabra clave es print & fold, la categoría es Make y muestra Page 2 of 2 con Print & Fold: map edition y Print & Fold. Este último es el evento de las 14:00 en North room, no el evento anterior con el mismo título. Elige Open this view in a new page debajo de los resultados. La nueva página debe mostrar los mismos controles, página, títulos, fechas y lugares. El enlace solo funciona mientras estén activos este laboratorio y su ruta de proxy. Es un ejemplo independiente, no tu trabajo ni un resultado de evaluación.
La vista Study independiente de abajo muestra los dos registros de la segunda página; compara también la fecha, la hora y el lugar, no solo los títulos.

Cierra las páginas adicionales del ejemplo. En la página de entrada de Fieldnote, elige Open Work view in a new page. Mantén abierta esta página de trabajo independiente. Vuelve a la pestaña Workspace del producto para editar archivos; cambiar de pestaña no es lo mismo que seguir un enlace del sitio ni pulsar Back en el navegador. Tu trabajo comienza incompleto aunque Study funcione.
cd cambia el directorio de esta terminal; una ruta absoluta empieza por /. pwd imprime el directorio actual. cat muestra un archivo de texto pequeño sin modificarlo:
cd /home/labex/project/fieldnote/work
pwd
cat catalog.js
Lee tres registros: FN-101 y FN-102 empiezan ambos el 2026-11-14 a las 10:00; FN-108 tiene el mismo título que FN-102, pero una fecha posterior. El orden de entrada del catálogo difiere deliberadamente del orden de visualización. Predice las tres primeras coincidencias para print & fold, categoría make, en orden cronológico: FN-101, FN-102, FN-104. Compara las identidades reales de los registros, no solo el número de tarjetas.
Filtraremos sin cambiar el catálogo. filter crea un nuevo array de objetos coincidentes. includes comprueba una subcadena literal; toLowerCase hace que la búsqueda no distinga mayúsculas de minúsculas. Busca en la única cadena concatenada title + ' ' + description, de modo que una coincidencia pueda cruzar ese espacio de unión. && exige que se cumplan ambas condiciones; || exige que se cumpla una de ellas. all admite cualquier categoría. La función flecha recibe cada evento.
sort modifica el array que ordena; ordenar el nuevo array filtrado conserva el catálogo preparado. Un comparador devuelve un número negativo, cero o un número positivo. La función auxiliar de abajo utiliza las comparaciones de cadenas < y > de JavaScript. El orden cronológico compara date + 'T' + time y después el ID ASCII del evento en caso de empate. El orden por título compara los títulos en minúsculas y después el ID. Este orden textual fijo no depende de la configuración regional del navegador ni de las posiciones originales del array. Las fechas preparadas comparten zona horaria; no estás implementando planificación entre zonas horarias.
La expresión condicional condition ? first : second elige un valor. Aquí primary || ... utiliza la comparación por ID solo cuando el resultado principal es cero. CATALOG es el objeto preparado que se carga antes de tus archivos. state es un objeto con q, category, sort y page; el siguiente paso hará que sus valores sean seguros y coherentes.
Guarda la función completa en selection.js. cat > filename reemplaza ese archivo. <<'JS', con el delimitador entre comillas, inicia un here-document: pega las líneas hasta el JS final, solo en su propia línea. Las comillas del delimitador mantienen literal la puntuación de JavaScript. Esto es editar y guardar mediante la terminal; no automatiza el editor de WebIDE.
cat > selection.js <<'JS'
function compare(a, b) {
return a < b ? -1 : a > b ? 1 : 0;
}
function matches(state) {
return CATALOG.events.filter(event =>
(state.category === 'all' || event.category === state.category) &&
(event.title + ' ' + event.description).toLowerCase()
.includes(state.q.toLowerCase())
).sort((a, b) => {
const primary = state.sort === 'title'
? compare(a.title.toLowerCase(), b.title.toLowerCase())
: compare(a.date + 'T' + a.time, b.date + 'T' + b.time);
return primary || compare(a.id, b.id);
});
}
JS
Vuelve a la página de trabajo independiente y actualízala. Espera a los controles y las tarjetas. Escribe print & fold en Keyword, selecciona Make en Category, conserva Soonest first y activa Search. Debes ver los tres registros previstos, distinguiendo los dos eventos de las 10:00 por sus títulos y lugares. Cambia Sort by a Title A–Z, envía de nuevo y espera Print & Fold, Print & Fold, Print & Fold: colour. Lee las fechas: los eventos con título idéntico son FN-102 y después FN-108. Cambiar solamente un selector edita un borrador; Search confirma los tres filtros juntos. La URL compartida aún no está completa.
Si no hay resultados, comprueba primero la palabra clave y la categoría. Un error como prnit no tiene coincidencias de forma legítima; cámbialo a print & fold y envía de nuevo. Si la página deja de responder tras editar, vuelve a Workspace, ejecuta cat selection.js, compara las llaves y las comillas con el bloque completo y reemplaza el archivo de nuevo. Actualiza la página de trabajo después de guardar; no es necesario reiniciar la VM.
Usa Check de este paso después de observar ambos órdenes. Ejecuta tus archivos originales de frontend en un entorno desechable de navegador y comprueba las relaciones entre registros completos; no completa la página activa ni cuenta Study como tu resultado.
Normalizar filtros y límites de página
En este paso, convertirás valores de borrador o entrantes en un estado predecible, incluidos los resultados vacíos y la última página disponible.
Permanece en la página de trabajo independiente. Con print & fold, Make, Soonest first, envía y elige Next. Hay cinco coincidencias y tres posiciones por página, por lo que la segunda contiene las dos últimas. Tres registros por página es un dato preparado, CATALOG.pageSize; el renderizador utiliza slice(start, end) para extraer una página sin modificar la lista completa. Los índices comienzan en cero y el final queda excluido: la primera página utiliza slice(0, 3) y la segunda slice(3, 6). Cada tarjeta utiliza el objeto de evento seleccionado, incluidos su propio ID, título, descripción, fecha, hora y lugar.
Ahora considera una página entrante 999, una categoría desconocida o una palabra clave con espacios en los extremos. Una función de normalización da un significado público a esos valores antes de dibujar. Una palabra clave ausente se convierte en una cadena vacía; elimina espacios solo de sus dos extremos, conservando los espacios internos y las mayúsculas del usuario. La coincidencia sigue sin distinguir mayúsculas. Una categoría ausente o desconocida se convierte en all; un orden ausente o desconocido, en soon. Convierte la página con Number: se permite un entero positivo seguro, incluida la forma equivalente 2.0. Los valores inválidos, cero, negativos, fraccionarios o no seguros se convierten en 1. Limita una página válida demasiado grande a la última página con coincidencias. Cero resultados sigue utilizando página 1 de 1, con ambos botones desactivados.
Number.isSafeInteger evita valores enteros imprecisos. Math.ceil redondea hacia arriba: cinco coincidencias divididas entre tres dan dos páginas. Math.max(1, ...) mantiene en uno las páginas de un resultado vacío; Math.min limita la página solicitada. Array.includes también comprueba la pertenencia a las listas de categorías y órdenes permitidos. Los valores predeterminados con || tratan las entradas ausentes o vacías. Un estado normalizado contiene valores, no texto de URL ya codificado.
Cambia a Workspace, todavía en /home/labex/project/fieldnote/work. Reemplaza state.js por esta función completa:
cat > state.js <<'JS'
function normalize(raw) {
const state = {
q: (raw.q || '').trim(),
category: CATALOG.categories.includes(raw.category) ? raw.category : 'all',
sort: ['soon', 'title'].includes(raw.sort) ? raw.sort : 'soon',
page: Number(raw.page || 1)
};
if (!Number.isSafeInteger(state.page) || state.page < 1) state.page = 1;
const pages = Math.max(1, Math.ceil(matches(state).length / CATALOG.pageSize));
state.page = Math.min(state.page, pages);
return state;
}
JS
Esto llama a tu función matches para calcular los límites después de filtrar. No ordena el catálogo original. Predice el estado de q: ' print & fold ', category: 'make', sort: 'soon', page: '999': palabra clave recortada, categoría y orden sin cambios, página 2. Con page: '2.5', la página es 1. Probarás los valores URL entrantes en el siguiente paso, cuando exista el lector.
Vuelve a la página independiente y actualízala. En Keyword, introduce dos espacios, después print & fold y otros dos espacios. Elige Make y activa Search. Tras enviar, el control debe contener print & fold sin espacios en los extremos. Elige Next: quedan solo la edición del mapa y el evento de las 14:00 en North room, y Next está desactivado. Desde la página 2, busca sound + light, Listen, Title A–Z. Esto confirma un filtro nuevo y vuelve a Page 1 of 1. El signo más literal forma parte del título, no es un espacio. Busca no such constellation: ninguna tarjeta, mensaje legible de ausencia de resultados, página 1 de 1 y ambos botones desactivados. Es un resultado correcto. Recupera una palabra clave y una categoría con coincidencias y envía.
Antes de Check, confirma que el catálogo original y todo el contenido de los registros siguen intactos. Las comprobaciones de valores desconocidos y formas numéricas de URL se incorporan en el siguiente paso; este comprueba el formulario normalizado y los límites de página alcanzables.
Leer y escribir la URL real de la vista
En este paso, harás que un enlace abierto directamente restaure los cuatro valores confirmados y sus registros visibles.
Una URL tiene un origen, una ruta, una consulta y un fragmento opcional después de #. El proxy de LabEx proporciona el origen y la ruta reales; no inventes otra base concatenando el origen y /. new URL(location.href) parte de la dirección real de esta página. Su searchParams lee valores de consulta decodificados con get y escribe valores normales, sin codificar, con set. Codifica separadores como el & de la palabra clave, y un signo más literal como %2B. Los espacios pueden serializarse como + o %20. Llamar a encodeURIComponent antes de set codificaría dos veces; pasa el valor original.
El curso solo controla q, category, sort y page. Conserva todos los parámetros extra y el fragmento existente: quedan fuera del estado del filtro y pueden pertenecer a la plataforma. Los valores ausentes o predeterminados pueden omitirse o escribirse explícitamente. El orden de los parámetros, las páginas numéricas equivalentes y las dos formas de espacio no cambian el significado. La inicialización puede normalizar una URL sin crear una visita extra. El controlador preparado realiza ese reemplazo en este laboratorio; el siguiente enseña las llamadas del historial.
El bucle for...of de abajo recorre las cuatro cadenas de claves conocidas. delete elimina cada valor anterior de una clave controlada antes de que el escritor establezca los valores no predeterminados.
En Workspace, guarda el lector y el escritor en url.js:
cat > url.js <<'JS'
function readURL() {
const params = new URL(location.href).searchParams;
return normalize({q: params.get('q'), category: params.get('category'),
sort: params.get('sort'), page: params.get('page')});
}
function stateURL(state) {
const url = new URL(location.href);
for (const key of ['q', 'category', 'sort', 'page']) url.searchParams.delete(key);
if (state.q) url.searchParams.set('q', state.q);
if (state.category !== 'all') url.searchParams.set('category', state.category);
if (state.sort !== 'soon') url.searchParams.set('sort', state.sort);
if (state.page !== 1) url.searchParams.set('page', state.page);
return url;
}
JS
Eliminar primero esas claves borra filtros antiguos al seleccionar un valor predeterminado, conservando las claves desconocidas. Las líneas condicionales omiten solo nuestros valores predeterminados. El escritor devuelve un objeto URL; por sí mismo no navega. El controlador preparado cambia la dirección antes de que el renderizador actualice el campo y el enlace reales para compartir.
Vuelve a la página de trabajo y actualízala. Busca print & fold, Make, Title A–Z, y elige Next. Lee el campo para compartir: representa los cuatro valores actuales, incluidos sort=title y page=2. Elige Open this view in a new page y actualiza la página nueva. Debes ver Print & Fold: map edition y después Print & Fold: tiny type, los dos últimos registros del orden por título. Comprueba sus fechas y lugares, no solo la cantidad. Es un enlace real a tus archivos de trabajo. Check independiente también lo abre en un contexto limpio sin depender de preferencias locales en caché.
Usa la barra de direcciones de la página independiente para probar estos valores entrantes. Conserva su origen real y la ruta /work/; reemplaza solo la consulta después de ?. Añade #directory-title al final para comprobar que se conserva el fragmento. Primero utiliza esta consulta:
?edition=november&platform=keep-me&q=print%20%26%20fold&category=make&sort=soon&page=999#directory-title
Debe mostrar la edición del mapa y el evento de North room de la segunda página, con Soonest first seleccionado. La dirección normalizada puede omitir sort=soon o usar + para los espacios; edition, platform y el fragmento permanecen. Ahora reemplaza la consulta por:
?edition=november&category=unknown&sort=unknown&page=2.5#directory-title
Espera una palabra clave vacía, All categories, Soonest first, Page 1 of 4 y los primeros registros FN-101, FN-102, FN-105. Un valor desconocido adopta el predeterminado; no es un error que exija reiniciar. Por último, una consulta con page=2.0 y la palabra clave/categoría de la primera prueba significa página 2. El orden y la escritura de los valores predeterminados son flexibles; el estado visible exacto y los registros completos no lo son.
Conserva la página independiente para leerla. Con un ancho cercano a 768 píxeles y después a 390, todos los filtros, controles de página, enlace compartido y texto completo de registros deben poder usarse sin desplazamiento horizontal. Pulsa Tab para observar el foco visible. La estructura y estos estilos están preparados; consérvalos. Ejecuta Check. Este laboratorio verifica restauración por URL directa, coherencia de URL/controles/resultados y límites seguros. Aún no exige restaurar con Back/Forward: su controlador preparado reemplaza la entrada actual y es el punto de partida del siguiente laboratorio.
Resumen
Seleccionaste registros de eventos completos con desempate estable, normalizaste filtros y límites de página, y leíste y escribiste cuatro valores en la URL real de la vista. Las formas equivalentes de URL reproducen la misma vista sin depender del orden original del catálogo. El siguiente laboratorio reemplaza el controlador preparado para que Back y Forward reales del navegador también restauren estas vistas.
