AbstractRenderer — API de la classe de base des renderers
Product Version : GeoLeaf Platform V2
Version : 2.0.0
Fichier source : packages/core/src/modules/utils/renderers/abstract-renderer.ts
Dernière mise à jour : mars 2026
Vue d'ensemble
AbstractRenderer est la classe de base commune à tous les renderers POI de GeoLeaf. Elle centralise :
- la résolution des dépendances (Log, Security, Config, Utils)
- les helpers de création d'éléments DOM
- l'enregistrement des event listeners avec cleanup automatique
- la gestion d'état par élément via WeakMap
- la gestion du cycle de vie (init / destroy)
Les renderers suivants étendent cette classe :
FieldRenderers— champs texte, badges, liens, tagsComponentRenderers— listes, tableaux, évaluationsMediaRenderers— images, galeries, vidéos
Constructeur
new AbstractRenderer(options?)| Paramètre | Type | Défaut | Description |
|---|---|---|---|
options.name | string | "Renderer" | Nom du renderer (utilisé dans les logs) |
options.config | object | {} | Configuration personnalisée |
options.debug | boolean | false | Active les logs debug |
class MyRenderer extends AbstractRenderer {
constructor(options = {}) {
super({
name: "MyRenderer",
debug: options.debug ?? false,
config: { showIcon: options.showIcon ?? true },
});
this.init();
}
render(data: unknown, options?: unknown): HTMLElement {
const el = this.createElement("div", "my-renderer");
// ... rendering logic
return el;
}
}Résolution des dépendances
getLog()
Retourne l'instance GeoLeaf.Log ou console en fallback.
const log = this.getLog();
log.info("[MyRenderer] rendering", data);getSecurity()
Retourne les utilitaires de sécurité (escapeHtml, setSafeHTML) avec fallbacks.
const sec = this.getSecurity();
const safe = sec.escapeHtml(userInput);
sec.setSafeHTML(element, htmlContent);getUtils()
Retourne GeoLeaf.Utils avec notamment resolveField.
const utils = this.getUtils();
const title = utils.resolveField(poi, "title", "label", "name");getActiveProfile()
Retourne le profil actif via GeoLeaf.Config.getActiveProfile().
const profile = this.getActiveProfile();
if (profile?.icons) {
// use profile icons
}Logs
log(level, message, ...args)
this.log("info", "Rendering POI", poi.title);
this.log("error", "Failed to load image", error);Méthodes de commodité
this.debug("Debug message"); // Only if debug=true
this.info("Info message");
this.warn("Warning message");
this.error("Error message");Format de sortie : [RendererName] Message ...args
Construction DOM
createElement(tagName, className, attributes?)
Crée un élément DOM avec classe(s) et attributs.
// Single class
const div = this.createElement("div", "my-class");
// Multiple classes
const btn = this.createElement("button", ["btn", "btn-primary"]);
// With attributes
const input = this.createElement("input", "field", {
type: "text",
placeholder: "Enter text",
"data-id": "123",
});createTextNode(text)
Crée un nœud texte sans injection HTML.
const node = this.createTextNode("Safe content");
element.appendChild(node);createTextElement(tagName, text, className?)
Crée un élément avec contenu texte sécurisé.
const p = this.createTextElement("p", "Description du POI", "poi-desc");
// <p class="poi-desc">Description du POI</p>createHTMLElement(tagName, html, className?)
Crée un élément avec HTML sanitisé via Security.setSafeHTML.
const div = this.createHTMLElement("div", "<strong>Titre</strong>", "content");appendChildren(parent, ...children)
Ajoute plusieurs enfants à un parent (chaînable).
this.appendChildren(
container,
this.createTextElement("h2", "Titre"),
this.createTextElement("p", "Description")
);Gestion des événements
addEventListener(element, event, handler, options?)
Enregistre un listener avec cleanup automatique à la destruction.
this.addEventListener(button, "click", (e) => {
console.log("Clicked!", e);
});
// Avec options
this.addEventListener(container, "scroll", this.handleScroll, { passive: true });
// Retourne une fonction de cleanup manuel
const cleanup = this.addEventListener(el, "mouseover", handler);
cleanup(); // cleanup immédiat si nécessaireremoveAllEventListeners()
Retire tous les listeners enregistrés. Appelé automatiquement par destroy().
Gestion d'état (WeakMap)
L'état est stocké par élément via WeakMap — il est automatiquement libéré quand l'élément est supprimé du DOM.
setState(element, state)
this.setState(container, {
poi: { id: "123", title: "Restaurant" },
renderTime: Date.now(),
});getState(element)
const state = this.getState(container);
console.log(state?.poi.title);updateState(element, updates)
Fusionne les mises à jour avec l'état existant.
this.updateState(container, { lastClicked: Date.now() });deleteState(element)
this.deleteState(container);Cycle de vie
init()
Initialise le renderer. À surcharger pour une initialisation personnalisée.
init() {
super.init(); // toujours appeler le parent
this.debug("Custom initialization");
}isInitialized()
if (!this.isInitialized()) {
this.warn("Renderer not ready");
return;
}destroy()
Libère toutes les ressources. À surcharger si nécessaire.
destroy() {
this.debug("Custom cleanup");
// custom cleanup here
super.destroy(); // toujours appeler le parent
}destroy() appelle automatiquement removeAllEventListeners() et vide le WeakMap d'état.
Méthode abstraite
render(data, options?)
Doit être implémentée par les sous-classes. Lance une erreur si non implémentée.
render(data: unknown, options?: unknown): HTMLElement {
const el = this.createElement("div", "my-renderer");
// ... rendering logic
return el;
}Exemple complet
class POICardRenderer extends AbstractRenderer {
constructor(options = {}) {
super({
name: "POICardRenderer",
debug: options.debug ?? false,
});
this.init();
}
render(poi: Record<string, unknown>): HTMLElement | null {
if (!poi) {
this.warn("No POI data provided");
return null;
}
const utils = this.getUtils();
const title = (utils.resolveField(poi, "title", "label", "name") as string) || "Sans titre";
const card = this.createElement("div", "poi-card", { "data-poi-id": String(poi.id) });
this.setState(card, { poi, renderTime: Date.now() });
const heading = this.createTextElement("h3", title, "poi-card__title");
card.appendChild(heading);
this.addEventListener(card, "click", () => {
card.dispatchEvent(
new CustomEvent("poi:select", {
detail: { poi },
bubbles: true,
})
);
});
return card;
}
destroy() {
this.debug("Destroying POICardRenderer");
super.destroy();
}
}Tests unitaires
describe("AbstractRenderer", () => {
let renderer: AbstractRenderer;
beforeEach(() => {
renderer = new AbstractRenderer({ name: "TestRenderer", debug: true });
});
afterEach(() => {
renderer.destroy();
});
test("createElement creates element with class", () => {
const div = renderer.createElement("div", "test-class");
expect(div.tagName).toBe("DIV");
expect(div.className).toBe("test-class");
});
test("addEventListener cleans up on destroy", () => {
const el = document.createElement("div");
const handler = jest.fn();
renderer.addEventListener(el, "click", handler);
el.click();
expect(handler).toHaveBeenCalledTimes(1);
renderer.destroy();
el.click();
expect(handler).toHaveBeenCalledTimes(1); // not called again
});
test("state management round-trip", () => {
const el = document.createElement("div");
renderer.setState(el, { count: 0 });
renderer.updateState(el, { count: 1 });
expect(renderer.getState(el).count).toBe(1);
renderer.deleteState(el);
expect(renderer.getState(el)).toBeNull();
});
});Modules liés
packages/core/src/modules/built-in/poi/renderers/field-renderers.tspackages/core/src/modules/built-in/poi/renderers/component-renderers.tspackages/core/src/modules/built-in/poi/renderers/media-renderers.tspackages/core/src/modules/built-in/poi/renderers/section-orchestrator.tspackages/core/src/modules/utils/general/dom-security.ts
Version : 2.0.0
Licence : MIT
Dernière mise à jour : mars 2026
