Files
Forge-Engine/static/js/play/offline/render-repeater.js
T
williamandClaude Sonnet 5 e08c53e042
Build and deploy / test-python (push) Successful in 6m55s
Build and deploy / test-js (push) Successful in 1m4s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Export Web/SCORM : corrige la résolution des champs "relation" hors ligne
Bug signalé : une Donnée liée/un filtre référençant un champ "relation"
affichait "{{champ}}" tel quel une fois exporté, alors qu'il fonctionnait
en ligne. Deux causes cumulées :

- full_game_payload.py lisait la colonne SQL "<champ>" au lieu de
  "<champ>_id" (seule vraie colonne d'un champ relation, voir
  _field_column dans filter_repeater_rows.py) en construisant
  gameData.data — une valeur toujours None. Invisible en ligne (les
  filtres y requêtent la base fraîche, jamais cette snapshot), mais
  fatal hors ligne (aucune base à requêter).
- Une fois ce None corrigé, le port JS (forgeFieldColumn) relisait
  cette même valeur sous une clé slugifiée+suffixée ("boss_id") alors
  que gameData.data est déjà indexé par le nom D'AFFICHAGE du champ
  ("boss") — mismatch qui ne se voyait que sur un champ relation (le
  seul cas où slugify(nom)+suffixe diverge du nom original). Supprime
  ce port erroné, lit directement row[fieldName] partout.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 10:02:26 +02:00

79 lines
3.8 KiB
JavaScript

// Export Web/SCORM — port de screens/rendering/render_repeater.py. Lit
// gameData.data/fields_meta (au lieu d'une requête SQL) et
// gameData.element_type_templates (voir full_game_payload.py — l'arbre
// d'un écran-modèle d'élément de jeu réutilisable, précalculé une fois à
// l'export) au lieu de screens/element_types/load_template_tree.py.
function forgeRenderRepeater(el, meta, gameData, childrenMap, ctx, parentFlexDirection) {
var style = forgeStyleString(el, parentFlexDirection, el.widget) + " overflow:auto;";
var attrsRaw = el.attributes || {};
var definitionId = attrsRaw._definition_id;
var definitionAttr = definitionId ? ' data-definition-id="' + forgeHtmlEscape(String(definitionId)) + '"' : "";
var templateTypeId = attrsRaw._template_type_id;
var template = forgeApplyCtx(el.content || "", ctx);
var children = (childrenMap && childrenMap[el.id]) || [];
if (!definitionId) {
return '<div id="' + forgeAutoId(el) + '" data-element-id="' + el.id + '" style="' + forgeHtmlEscape(style) +
'"><em>Répéteur : choisis un objet de données dans les propriétés.</em></div>';
}
definitionId = String(definitionId);
var fieldsMetaForDef = gameData.fields_meta && gameData.fields_meta[definitionId];
if (!fieldsMetaForDef) {
return '<div id="' + forgeAutoId(el) + '" data-element-id="' + el.id + '" style="' + forgeHtmlEscape(style) +
'"><em>Objet introuvable — a-t-il été supprimé ?</em></div>';
}
var rows = gameData.data[definitionId] || [];
rows = forgeFilterRepeaterRows(gameData, definitionId, rows, attrsRaw);
var templateTree = null;
if (templateTypeId) {
templateTree = gameData.element_type_templates && gameData.element_type_templates[String(templateTypeId)];
}
var items = rows.map(function (row) {
var rowCtx = Object.assign({}, ctx || {});
// gameData.data est déjà indexé par le nom D'AFFICHAGE du champ (voir
// full_game_payload.py), jamais par sa colonne SQL — row[f.name] lit
// donc directement la bonne valeur, y compris pour un champ "relation"
// (voir filter-repeater-rows.js pour le détail de ce piège).
fieldsMetaForDef.forEach(function (f) {
var v = row[f.name];
rowCtx[f.name] = v === null || v === undefined ? "" : String(v);
});
var inner;
if (templateTree) {
inner = templateTree.top.map(function (c) {
return forgeRenderElementHtml(c, gameData, templateTree.children_map, rowCtx);
}).join("");
} else if (children.length) {
inner = children.map(function (c) {
return forgeRenderElementHtml(c, gameData, childrenMap, rowCtx);
}).join("");
} else {
var line = template;
Object.keys(rowCtx).forEach(function (fname) {
if (fname === "_forge_play_mode") return;
line = line.split("{{" + fname + "}}").join(rowCtx[fname]);
});
inner = forgeHtmlEscape(line);
}
return '<div class="repeaterItem" data-row-id="' + row.id + '">' + inner + '</div>';
});
var emptyMsg = attrsRaw._filtre_champ ? "Aucune ligne ne correspond au filtre réglé." : "Aucune donnée dans cet objet pour l'instant.";
var innerAll = items.length ? items.join("") : "<em>" + emptyMsg + "</em>";
return '<div id="' + forgeAutoId(el) + '" data-element-id="' + el.id + '"' + definitionAttr +
' style="' + forgeHtmlEscape(style) + '">' + innerAll + '</div>';
}
if (typeof module !== 'undefined' && module.exports) {
module.exports = { forgeRenderRepeater };
var renderMod = require('./render-element.js');
["forgeStyleString", "forgeHtmlEscape", "forgeAutoId", "forgeApplyCtx", "forgeRenderElementHtml"].forEach(function (name) {
global[name] = global[name] || renderMod[name];
});
var filterMod = require('./filter-repeater-rows.js');
global.forgeFilterRepeaterRows = global.forgeFilterRepeaterRows || filterMod.forgeFilterRepeaterRows;
}