Introduction
Fieldnote est un petit guide d'événements pour le week-end. Un lien de recherche utile doit décrire la vue : mot-clé, catégorie, ordre et page de résultats. Une personne qui ouvre ce lien doit retrouver les mêmes événements, avec l'heure et le lieu corrects pour deux événements portant le même titre. Vous allez construire cette relation entre états autour d'un site adaptatif préparé et d'un petit catalogue local.
Vous devez connaître les chemins de base du terminal, les formulaires HTML, la mise en page et le focus CSS, ainsi que les tableaux, objets, fonctions, la sélection DOM et les événements JavaScript. Cet atelier présente le filtrage de tableaux, les comparateurs, les limites de pagination, URL et URLSearchParams. La recherche et l'analyse d'URL ont de nombreux usages ; ici, le résultat est une vue complète et reproductible à quatre valeurs. Le moteur d'affichage préparé crée les cartes à partir des enregistrements sélectionnés par votre code. Aucune connaissance de backend ou de framework n'est nécessaire. Tous les événements sont synthétiques. Les 55 minutes sont une estimation de l'auteur incluant la lecture et les erreurs courantes.
Utilisez Workspace pour les commandes et Fieldnote pour la page d'entrée. Cet atelier démarre indépendamment. Modifiez uniquement /home/labex/project/fieldnote/work/selection.js, state.js et url.js ; app.js est le contrôleur préparé ici et deviendra votre travail dans l'atelier suivant. Conservez catalog.js, view.js, index.html, style.css, la vue Study indépendante et le service préparé. Le contrôleur envoie déjà les formulaires et redessine la page, en utilisant replaceState pour actualiser l'entrée courante. L'historique est ici un comportement préparé, pas encore un objectif d'apprentissage. Les libellés du site restent en anglais.
Sélectionner des enregistrements réels dans un ordre stable
Dans cette étape, vous ouvrirez une vue partagée fonctionnelle, puis donnerez à votre page de travail un comportement prévisible de mot-clé, de catégorie et de tri.
Dans Fieldnote, choisissez Open Study view in a new page. Ce lien réel ouvre une page autonome du navigateur, hors de l'onglet intégré du produit. Utilisez cette nouvelle page pour les actions du site. Son adresse contient q, category, sort et page ; son mot-clé est print & fold, sa catégorie Make, et elle affiche Page 2 of 2 avec Print & Fold: map edition et Print & Fold. Ce dernier est l'événement de 14:00 dans North room, et non l'événement antérieur du même nom. Choisissez Open this view in a new page sous les résultats. La nouvelle page doit afficher les mêmes contrôles, page, titres, dates et lieux. Le lien fonctionne seulement tant que cet atelier et sa route proxy sont actifs. C'est un exemple indépendant, pas votre travail ni un résultat de vérification.
La vue Study indépendante ci-dessous présente les deux enregistrements de la deuxième page ; comparez la date, l'heure et le lieu autant que les titres.

Fermez les pages supplémentaires de l'exemple. Sur la page d'entrée de Fieldnote, choisissez Open Work view in a new page. Gardez cette page de travail autonome ouverte. Revenez à l'onglet Workspace du produit pour modifier les fichiers ; changer d'onglet diffère du suivi d'un lien du site ou de l'utilisation du bouton Back du navigateur. Votre travail commence incomplet même si Study fonctionne.
cd change le répertoire de ce terminal ; un chemin absolu commence par /. pwd affiche le répertoire courant. cat affiche un petit fichier texte sans le modifier :
cd /home/labex/project/fieldnote/work
pwd
cat catalog.js
Lisez trois enregistrements : FN-101 et FN-102 commencent tous deux le 2026-11-14 à 10:00 ; FN-108 porte le même titre que FN-102, mais à une date ultérieure. L'ordre d'entrée du catalogue diffère volontairement de l'ordre d'affichage. Prévoyez les trois premières correspondances pour print & fold, catégorie make, dans l'ordre chronologique : FN-101, FN-102, FN-104. Comparez les identités effectives des enregistrements, pas seulement le nombre de cartes.
Nous allons filtrer sans changer le catalogue. filter crée un nouveau tableau d'objets correspondants. includes teste une sous-chaîne littérale ; toLowerCase rend la recherche insensible à la casse. Cherchez dans l'unique chaîne concaténée title + ' ' + description : une correspondance peut donc traverser cet espace de jonction. && exige les deux conditions ; || exige l'une ou l'autre. all accepte toutes les catégories. La fonction fléchée reçoit chaque événement.
sort modifie le tableau qu'il trie ; trier le nouveau tableau filtré préserve le catalogue préparé. Un comparateur renvoie un nombre négatif, zéro ou un nombre positif. La fonction auxiliaire ci-dessous utilise les comparaisons de chaînes JavaScript < et >. L'ordre chronologique compare date + 'T' + time, puis l'ID ASCII en cas d'égalité. L'ordre par titre compare les titres en minuscules, puis l'ID. Cet ordre textuel fixe ne dépend ni de la langue du navigateur ni des positions initiales du tableau. Les dates préparées partagent un même fuseau ; vous n'implémentez pas de planification entre fuseaux horaires.
L'expression conditionnelle condition ? first : second choisit une valeur. Ici, primary || ... utilise la comparaison d'ID uniquement quand le résultat principal vaut zéro. CATALOG est l'objet préparé chargé avant vos fichiers. state est un objet contenant q, category, sort et page ; l'étape suivante rendra ses valeurs sûres et cohérentes.
Enregistrez la fonction complète dans selection.js. cat > filename remplace ce fichier. Le délimiteur entre guillemets <<'JS' commence un here-document : collez les lignes jusqu'au JS final, seul sur sa ligne. Les guillemets conservent littéralement la ponctuation JavaScript. Il s'agit d'édition et d'enregistrement dans le terminal, pas d'une automatisation de l'éditeur 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
Revenez à la page de travail et actualisez-la. Attendez les contrôles et les cartes. Saisissez print & fold dans Keyword, sélectionnez Make sous Category, conservez Soonest first, puis activez Search. Vous devez voir les trois enregistrements prévus, les deux événements de 10:00 se distinguant par leurs titres et leurs lieux. Passez Sort by à Title A–Z, envoyez de nouveau et attendez Print & Fold, Print & Fold, Print & Fold: colour. Lisez les dates : les événements de même titre sont FN-102, puis FN-108. Modifier seulement une liste édite un brouillon ; Search valide les trois filtres ensemble. L'URL partagée n'est pas encore complète.
Si les résultats sont vides, contrôlez d'abord le mot-clé et la catégorie. Une faute comme prnit n'a légitimement aucune correspondance ; remplacez-la par print & fold, puis envoyez de nouveau. Si la page ne répond plus après une modification, revenez dans Workspace, exécutez cat selection.js, comparez accolades et guillemets au bloc complet et remplacez à nouveau le fichier. Actualisez après l'enregistrement ; inutile de réinitialiser la VM.
Utilisez Check de cette étape après avoir observé les deux ordres. La vérification exécute vos fichiers frontend d'origine dans un environnement de navigateur jetable et contrôle les relations entre enregistrements complets ; elle ne complète pas la page active et ne compte pas Study comme votre résultat.
Normaliser les filtres et les limites de page
Dans cette étape, vous transformerez les valeurs de brouillon ou entrantes en un état prévisible, y compris pour les résultats vides et la dernière page disponible.
Restez dans la page de travail autonome. Avec print & fold, Make, Soonest first, envoyez et choisissez Next. Il y a cinq correspondances et trois places par page : la deuxième contient les deux dernières. Trois enregistrements par page est une donnée préparée, CATALOG.pageSize ; le moteur utilise slice(start, end) pour extraire une page sans changer la liste entière. Les indices commencent à zéro et la fin est exclue : la première page utilise slice(0, 3), la deuxième slice(3, 6). Chaque carte utilise l'objet événement sélectionné, avec ses propres ID, titre, description, date, heure et lieu.
Considérez maintenant une page entrante 999, une catégorie inconnue ou un mot-clé entouré d'espaces. Une fonction de normalisation donne à ces valeurs une signification publique avant l'affichage. Un mot-clé absent devient vide ; retirez uniquement ses espaces extérieurs, en conservant les espaces internes et la casse de l'utilisateur. La recherche reste insensible à la casse. Une catégorie absente ou inconnue devient all ; un tri absent ou inconnu devient soon. Convertissez la page avec Number : un entier positif sûr est autorisé, y compris la forme équivalente 2.0. Les valeurs invalides, égales à zéro, négatives, non entières ou non sûres deviennent 1. Limitez une page valide trop grande à la dernière page de correspondances. Zéro résultat conserve une page 1 sur 1, avec les deux boutons désactivés.
Number.isSafeInteger évite les valeurs entières imprécises. Math.ceil arrondit vers le haut : cinq correspondances divisées par trois donnent deux pages. Math.max(1, ...) maintient à un le nombre de pages d'un résultat vide ; Math.min limite la page demandée. Array.includes contrôle aussi l'appartenance aux listes de catégories et de tris permis. Les valeurs par défaut avec || traitent les entrées absentes ou vides. Un état normalisé contient des valeurs, pas du texte d'URL déjà encodé.
Passez dans Workspace, toujours dans /home/labex/project/fieldnote/work. Remplacez state.js par cette fonction complète :
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
Elle appelle votre fonction matches pour calculer les limites après filtrage. Elle ne trie pas le catalogue initial. Prévoyez l'état pour q: ' print & fold ', category: 'make', sort: 'soon', page: '999' : mot-clé sans espaces extérieurs, catégorie et tri inchangés, page 2. Pour page: '2.5', ce sera la page 1. Vous testerez les valeurs URL entrantes à l'étape suivante, quand le lecteur sera présent.
Revenez à la page autonome et actualisez. Dans Keyword, saisissez deux espaces, puis print & fold, puis deux autres espaces. Choisissez Make et activez Search. Après envoi, le contrôle doit contenir print & fold sans espaces extérieurs. Choisissez Next : seules restent l'édition du plan et la séance de 14:00 dans North room, et Next est désactivé. Depuis la page 2, recherchez sound + light, Listen, Title A–Z. Cela valide de nouveaux filtres et revient à Page 1 of 1. Le signe plus littéral appartient au titre ; ce n'est pas un espace. Recherchez no such constellation : aucune carte, un message lisible sans résultat, page 1 sur 1 et les deux boutons désactivés. C'est un résultat correct. Rétablissez un mot-clé et une catégorie correspondants, puis envoyez.
Avant Check, confirmez que le catalogue original et tout le contenu des enregistrements sont préservés. Les contrôles des valeurs inconnues et des écritures numériques d'URL rejoindront cette vérification à l'étape suivante ; celle-ci vérifie le formulaire normalisé et les limites atteignables.
Lire et écrire l'URL réelle de la vue
Dans cette étape, vous ferez restaurer par un lien ouvert directement les quatre valeurs validées et leurs enregistrements visibles.
Une URL comporte une origine, un chemin, une requête et un fragment facultatif après #. Le proxy LabEx fournit l'origine et le chemin réels ; n'inventez pas une base en ajoutant / à l'origine. new URL(location.href) part de l'adresse réelle de cette page. Son searchParams lit les valeurs de requête décodées avec get et écrit des valeurs ordinaires non encodées avec set. Il encode les séparateurs comme le & du mot-clé et le plus littéral en %2B. Les espaces peuvent être sérialisés en + ou %20. Appeler encodeURIComponent avant set encoderait deux fois ; transmettez la valeur d'origine.
Le cours ne gère que q, category, sort et page. Préservez tous les paramètres supplémentaires et le fragment existant : ils sont extérieurs à l'état du filtre et peuvent appartenir à la plateforme. Les valeurs absentes ou par défaut peuvent être omises ou explicites. L'ordre des paramètres, les pages numériques équivalentes et les deux écritures d'espace ne changent pas le sens. L'initialisation peut normaliser une URL sans créer de visite supplémentaire. Le contrôleur préparé effectue ce remplacement ici ; l'atelier suivant enseigne les appels d'historique.
La boucle for...of ci-dessous parcourt les quatre chaînes de clés connues. delete enlève chaque ancienne valeur gérée avant que le générateur définisse les valeurs non par défaut.
Dans Workspace, enregistrez le lecteur et le générateur dans 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
Supprimer d'abord ces clés élimine les anciens filtres lorsqu'une valeur par défaut est choisie, tout en préservant les clés inconnues. Les lignes conditionnelles omettent seulement nos valeurs par défaut. Le générateur renvoie un objet URL ; il ne déclenche pas lui-même une navigation. Le contrôleur préparé change l'adresse avant que le moteur actualise le vrai champ et le vrai lien de partage.
Revenez à la page de travail et actualisez. Recherchez print & fold, Make, Title A–Z, puis choisissez Next. Lisez le champ de partage : les quatre valeurs sont représentées, dont sort=title et page=2. Choisissez Open this view in a new page et actualisez cette nouvelle page. Attendez Print & Fold: map edition, puis Print & Fold: tiny type, les deux derniers enregistrements dans l'ordre des titres. Contrôlez leurs dates et lieux, pas seulement leur nombre. C'est un véritable lien vers vos fichiers de travail. Le Check indépendant l'ouvre aussi dans un contexte propre, sans dépendre de préférences locales en cache.
Utilisez la barre d'adresses de la page autonome pour ces essais entrants. Gardez son origine réelle et le chemin /work/ ; remplacez uniquement la requête après ?. Ajoutez #directory-title après la requête pour vérifier la conservation d'un fragment. Utilisez d'abord :
?edition=november&platform=keep-me&q=print%20%26%20fold&category=make&sort=soon&page=999#directory-title
La page doit montrer l'édition du plan et l'événement de North room de la deuxième page, avec Soonest first sélectionné. L'adresse normalisée peut omettre sort=soon ou utiliser + pour les espaces ; edition, platform et le fragment restent présents. Remplacez maintenant la requête par :
?edition=november&category=unknown&sort=unknown&page=2.5#directory-title
Attendez un mot-clé vide, All categories, Soonest first, Page 1 of 4 et les premiers enregistrements FN-101, FN-102, FN-105. Une valeur inconnue prend la valeur par défaut ; ce n'est pas une erreur nécessitant une réinitialisation. Enfin, une requête avec page=2.0 et le mot-clé/la catégorie du premier essai signifie aussi page 2. L'ordre et l'écriture des valeurs par défaut sont flexibles ; l'état visible exact et les enregistrements complets ne le sont pas.
Conservez la page autonome pour la lire. À une largeur proche de 768 pixels, puis de 390 pixels, filtres, contrôles de page, lien de partage et textes complets doivent rester utilisables sans défilement horizontal. Appuyez sur Tab pour observer le focus visible. La structure et ces styles sont préparés ; conservez-les. Lancez Check. Cet atelier vérifie l'ouverture directe d'URL, la cohérence URL/contrôles/résultats et les limites sûres. Il n'exige pas encore la restauration Back/Forward : son contrôleur préparé remplace l'entrée courante et sert de point de départ à l'atelier suivant.
Résumé
Vous avez sélectionné des enregistrements complets avec un départage stable, normalisé filtres et limites de page, puis lu et écrit quatre valeurs dans l'URL réelle. Des écritures URL équivalentes reproduisent la même vue sans dépendre de l'ordre d'origine du catalogue. Le prochain atelier remplace le contrôleur préparé pour que les vrais Back et Forward du navigateur restaurent aussi ces vues.
