Content Builder — Documentation Complète
Version: 2.0.0
Date: mars 2026
Module: GeoLeaf._ContentBuilder
Table des Matières
Vue d'ensemble
Le Content Builder est le système de génération de contenu HTML pour les POIs (Points d'Intérêt) dans GeoLeaf. Il gère la création de :
- Popups : Fenêtres contextuelles sur la carte
- Tooltips : Info-bulles au survol
- Panels : Panneaux latéraux détaillés
Architecture Modulaire
Le Content Builder utilise une architecture modulaire en TypeScript :
built-in/ui/content-builder/
├── core.ts // Helpers, validators, badge resolver, formatters
├── templates.ts // Orchestrateur — re-exporte tous les template builders
├── templates-css-classes.ts // Constantes CSS BEM
├── templates-primitives.ts // Primitives HTML (wrapInDiv, buildLabel...)
├── templates-text-metric.ts // Templates texte/métrique/rating/badge
├── templates-media-collection.ts // Templates image/list/table/gallery/coordinates
├── assemblers.ts // Assembleurs popup/tooltip/panel
├── helpers.ts // Helpers utilitaires partagés
├── renderers-collection.ts // Renderers : list, table, tags, gallery, reviews
├── renderers-geo.ts // Renderers : coordonnées, géométrie
├── renderers-numeric.ts // Renderers : number, metric, rating
├── renderers-shared.ts // Logique renderer partagée
├── renderers-text.ts // Renderers : text, longtext, link, badge, image
└── renderers-visual.ts // Renderers visuelsLe module principal (content-builder.ts) importe et orchestre l'ensemble.
Avantages :
- Modularité : Chaque module a une responsabilité claire
- Testabilité : Modules isolés testables individuellement
- Maintenabilité : Fichiers <400 lignes, code organisé
- Réutilisabilité : Templates et helpers réutilisables
- Sécurité :
escapeHtmletvalidateUrlimportés depuisbuilt-in/security/index.ts
Architecture
Diagramme de Flux
USER INTERACTION
(Click POI, Hover, Open Panel)
│
▼
POI Handler (built-in/poi/*)
Calls: buildPopupHTML / buildTooltipHTML / Panel
│
▼
CONTENT BUILDER ORCHESTRATOR
(content-builder.ts)
│
├── buildPopupHTML(poi, config, options)
├── buildTooltipHTML(poi, config, options)
└── buildPanelItems(poi, config, options)
│
▼
Assemblers Module (assemblers.ts)
- buildPopupHTML
- buildTooltipHTML
- buildPanelItems
│
▼
Renderers (13+ types)
- renderText, renderBadge, renderImage
- renderList, renderTable, renderTags
- renderCoordinates, renderGallery, etc.
│
▼
Core + Templates Modules
- Helpers, validators, badge resolver
- Template builders HTML
│
▼
HTML CONTENT GENERATED
(Popup, Tooltip, Panel items)Modules
1. core.ts
Responsabilité : Fonctions utilitaires, validateurs, résolution badges, formatage.
Exports :
GeoLeaf._ContentBuilder.Core = {
// Helpers
getResolveField(), // POI field resolution with multi-path support
getEscapeHtml(), // Secure HTML escaping (from built-in/security)
getActiveProfile(), // Active profile config
getLog(), // System logger
// Validators
validateImageUrl(poi, field, options),
validateCoordinates(poi, field),
validateNumber(value, options),
validateRating(value, options),
// Badge Resolver
resolveBadge(poi, config, options),
resolveBadgeTooltip(badge),
// Formatters
formatNumber(value, options),
formatCoordinates(lat, lng, format),
formatRating(value, max),
}Exemple :
const Core = GeoLeaf._ContentBuilder.Core;
// Image URL validation
const imageUrl = Core.validateImageUrl(poi, "attributes.photo", {
defaultImage: "/images/default.jpg",
});
// Badge resolution
const badge = Core.resolveBadge(
poi,
{ field: "attributes.categoryId" },
{ context: "popup", resolveCategoryDisplay: true }
);
// Returns: { icon, color, label, tooltip }
// Coordinate formatting
const formatted = Core.formatCoordinates(45.7578, 4.832);
// Returns: "45°45'28.1\"N, 4°49'55.2\"E"2. templates.ts
Responsabilité : Orchestrateur des template builders HTML — importe et re-exporte les sous-modules.
Architecture des sous-modules :
templates-css-classes.ts— constantes CSS BEM (40+ classes)templates-primitives.ts—buildLabel,wrapInParagraph,wrapInDivtemplates-text-metric.ts—createTextElement,createMetricElement,createBadge,createRatingElement,createLinkElementtemplates-media-collection.ts—createImageElement,createListElement,createTableElement,createGalleryElement,createCoordinatesElement
Exports :
GeoLeaf._ContentBuilder.Templates = {
CSS_CLASSES: { /* 40+ BEM class constants */ },
// Primitives
buildLabel(label, options),
wrapInParagraph(content, className),
wrapInDiv(content, className),
// Text / Metric
createTextElement(value, options),
createLongtextElement(value, options),
createNumberElement(value, options),
createMetricElement(value, options),
createBadge(badge, options),
createStar(filled),
createRatingElement(value, options),
createLinkElement(href, label, options),
// Media / Collection
createImageElement(src, options),
createListElement(items, options),
createTableElement(data, options),
createTag(label, options),
createTagsElement(tags, options),
createCoordinatesElement(lat, lng, options),
createGalleryElement(images, options),
}Exemple :
const Templates = GeoLeaf._ContentBuilder.Templates;
// Create metric
const metricHtml = Templates.createMetricElement(42.5, {
suffix: "/m²",
icon: "fa-euro",
});
// Returns: <div class="gl-metric">42.5 /m²</div>
// Create rating
const ratingHtml = Templates.createRatingElement(4.5, { max: 5 });
// Returns: <div class="gl-rating">4.5/5</div>
// Create badge
const badgeHtml = Templates.createBadge({
label: "Restaurant",
icon: "fa-utensils",
color: "#e74c3c",
tooltip: "Catégorie: Restaurants",
});3. assemblers.ts
Responsabilité : Assembler les renderers en structures complètes (popup, tooltip, panel).
Exports :
GeoLeaf._ContentBuilder.Assemblers = {
buildPopupHTML(poi, config, options), // Full popup HTML
buildTooltipHTML(poi, config, options), // Full tooltip HTML
buildPanelItems(poi, config, options), // Side panel items array
}Exemple :
const Assemblers = GeoLeaf._ContentBuilder.Assemblers;
// Build Popup
const popupHtml = Assemblers.buildPopupHTML(
poi,
[
{ type: "image", field: "photo", variant: "hero", order: 1 },
{ type: "badge", field: "categoryId", order: 2 },
{ type: "text", field: "name", variant: "title", order: 3 },
{ type: "longtext", field: "description", order: 4 },
],
{ context: "popup" }
);
// Build Tooltip
const tooltipHtml = Assemblers.buildTooltipHTML(poi, [
{ type: "text", field: "name", order: 1 },
{ type: "badge", field: "categoryId", order: 2, contentUnion: " - " },
]);
// Returns: "Restaurant La Bonne Table - Restaurant"
// Build Panel Items
const panelItems = Assemblers.buildPanelItems(poi, [
{ type: "image", field: "photo", order: 1 },
{ type: "list", field: "services", label: "Services", accordion: true },
{ type: "table", field: "schedule", label: "Horaires" },
]);
// Returns: [{ html, config, label, accordion, defaultOpen }, ...]4. content-builder.ts (orchestrateur)
Responsabilité : 13 renderers individuels + orchestration centrale.
Types de renderers :
| Type | Description | Exemple champ |
|---|---|---|
text | Texte simple (variants: title, short, long) | name, title |
longtext | Texte multiligne | description, notes |
number | Valeur numérique formatée | price, area, quantity |
metric | Nombre avec préfixe/suffixe | € 89.50 /nuit |
rating | Note avec étoiles | 4.5/5 ★ |
badge | Badge taxonomie | Catégorie, Sous-catégorie |
image | Image (variants: default, hero) | photo, thumbnail |
link | Lien hypertexte | website, booking_url |
list | Liste HTML | services[], amenities[] |
table | Tableau données | schedule, pricing |
tags | Cloud de tags | keywords[], tags[] |
coordinates | GPS (DMS format) | latitude, longitude |
gallery | Galerie images | photos[], gallery[] |
Exports :
GeoLeaf._ContentBuilder = {
// Individual renderers (13)
renderText(poi, config, options),
renderLongtext(poi, config, options),
renderNumber(poi, config, options),
renderMetric(poi, config, options),
renderRating(poi, config, options),
renderBadge(poi, config, options),
renderImage(poi, config, options),
renderLink(poi, config, options),
renderList(poi, config, options),
renderTable(poi, config, options),
renderTags(poi, config, options),
renderCoordinates(poi, config, options),
renderGallery(poi, config, options),
renderItem(poi, config, options), // Dispatcher by type
// Assemblers (delegated to assemblers.ts)
buildPopupHTML(poi, config, options),
buildTooltipHTML(poi, config, options),
buildPanelItems(poi, config, options),
// Utilities
getResolveField(),
getEscapeHtml(),
getActiveProfile(),
}API Reference
renderItem(poi, config, options)
Dispatcher principal. Appelle le renderer correspondant selon config.type.
Signature :
const html = GeoLeaf._ContentBuilder.renderItem(poi, config, options);Paramètres :
poi(Object) : Données du POIconfig(Object) :type(string) : Type de renderer ("text","image","badge", etc.)field(string) : Chemin du champ (dot notation)label(string, optionnel) : Libellé à afficherorder(number, optionnel) : Ordre d'affichagevariant(string, optionnel) : Variante du renderer
options(Object, optionnel) :context:"popup"|"tooltip"|"panel"(défaut:"popup")
Retourne : string HTML
Exemple :
const html = GeoLeaf._ContentBuilder.renderItem(
poi,
{ type: "text", field: "attributes.name", variant: "title" },
{ context: "popup" }
);Core.getResolveField()
Retourne une fonction de résolution de champs POI avec support multi-paths.
const resolveField = GeoLeaf._ContentBuilder.Core.getResolveField();
// Test in order: poi.attributes.name → poi.attributes.nom → poi.properties.name
const name = resolveField(poi, "attributes.name", "attributes.nom", "properties.name");Core.validateImageUrl(poi, field, options)
Valide et normalise une URL d'image. Utilise validateUrl() du module built-in/security.
Paramètres :
poi(Object) : POI sourcefield(string) : Champ à valideroptions(Object) :defaultImage(string) : Image par défaut si invalideallowedPatterns(Array<RegExp>) : Patterns autorisés (défaut:https://,http://,data:image/)
Retourne : URL validée ou defaultImage
Exemples d'utilisation
Exemple 1 : Popup standard
const layout = [
{ type: "image", field: "photo", variant: "hero", order: 1 },
{ type: "badge", field: "categoryId", order: 2 },
{ type: "text", field: "name", variant: "title", order: 3 },
{ type: "metric", field: "price", label: "Prix", suffix: "€/nuit", order: 4 },
{ type: "rating", field: "rating", order: 5 },
{ type: "link", field: "website", label: "Réserver", order: 6 },
];
const popupHtml = GeoLeaf._ContentBuilder.buildPopupHTML(poi, layout, { context: "popup" });Exemple 2 : Tooltip minimal
const tooltipLayout = [
{ type: "text", field: "name", order: 1 },
{ type: "badge", field: "categoryId", order: 2, contentUnion: " — " },
];
const tooltipHtml = GeoLeaf._ContentBuilder.buildTooltipHTML(poi, tooltipLayout);Exemple 3 : Panneau détail avec accordéons
const panelLayout = [
{ type: "image", field: "photo", variant: "hero", order: 1 },
{ type: "badge", field: "categoryId", order: 2 },
{ type: "text", field: "name", variant: "title", order: 3 },
{ type: "longtext", field: "description", label: "Description", accordion: true, order: 4 },
{ type: "list", field: "amenities", label: "Équipements", accordion: true, order: 5 },
{ type: "table", field: "schedule", label: "Horaires", order: 6 },
{ type: "coordinates", field: "coordinates", label: "Position GPS", order: 7 },
];
const panelItems = GeoLeaf._ContentBuilder.buildPanelItems(poi, panelLayout);
// panelItems: [{ html, config, label, accordion, defaultOpen }, ...]Exemple 4 : Renderer individuel
// Render a gallery directly
const galleryHtml = GeoLeaf._ContentBuilder.renderGallery(
poi,
{ field: "attributes.photos", maxItems: 6 },
{ context: "panel" }
);
// Render coordinates
const coordHtml = GeoLeaf._ContentBuilder.renderCoordinates(poi, {
field: "geometry.coordinates",
format: "dms",
});Best Practices
Sécurité
// All text fields are automatically escaped via escapeHtml() from built-in/security
// Do not bypass this — never pass unsanitized poi.properties directly to innerHTML
// Good: use renderItem
const html = GeoLeaf._ContentBuilder.renderItem(poi, { type: "text", field: "name" });
// Bad: manual innerHTML with raw data
container.innerHTML = poi.attributes.name; // XSS riskPerformance
// Build all content at once with buildPopupHTML (single pass)
const html = GeoLeaf._ContentBuilder.buildPopupHTML(poi, layout);
// Avoid calling renderItem in a loop for large panels
// Prefer buildPanelItems which batches renderingOrdre des items
// Items are sorted by "order" property before rendering
const layout = [
{ type: "text", field: "name", order: 1 },
{ type: "rating", field: "rating", order: 3 },
{ type: "badge", field: "categoryId", order: 2 }, // will be rendered second
];Troubleshooting
Champ vide dans le rendu
// Verify field path with resolveField
const resolveField = GeoLeaf._ContentBuilder.Core.getResolveField();
const value = resolveField(poi, "attributes.rating");
console.log("Field value:", value);
// Enable debug logging
GeoLeaf.Config.setDebug({ enabled: true, modules: ["content-builder"] });Image non affichée
// validateImageUrl returns null if URL is invalid
const imageUrl = GeoLeaf._ContentBuilder.Core.validateImageUrl(poi, "attributes.photo", {
defaultImage: "/images/placeholder.jpg",
});
console.log("Validated image URL:", imageUrl);Badge sans texte
// Verify taxonomy is loaded
const profile = GeoLeaf.Config.getActiveProfile();
console.log("Taxonomy:", profile?.taxonomy?.categories);
// The badge resolver needs taxonomy to resolve categoryId → label
const badge = GeoLeaf._ContentBuilder.Core.resolveBadge(
poi,
{ field: "attributes.categoryId" },
{ context: "popup", resolveCategoryDisplay: true }
);
console.log("Badge resolved:", badge);Version : 2.0.0
Dernière mise à jour : mars 2026
