From 438e561b45254be567f7453dce48bbdb824e9097 Mon Sep 17 00:00:00 2001 From: william Date: Tue, 25 Aug 2026 18:17:35 +0200 Subject: [PATCH] =?UTF-8?q?Pr=C3=A9server=20l'=C3=A9tat=20visuel=20transit?= =?UTF-8?q?oire=20lors=20du=20rafra=C3=AEchissement=20des=20donn=C3=A9es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cause réelle du bug rapporté ("clic sur une ligne = comme un rechargement, impossible de cliquer sur une deuxième ligne") : applyOpenRowBindings() regénère entièrement le sous-arbre d'un élément contenant des {{champ}} (ex. "mail content") à partir de son HTML D'ORIGINE, tel que rendu par le serveur — donc avec son style PAR DÉFAUT (ici "invisible" via le réglage Disposition > Visibilité). Cette fonction est appelée après CHAQUE rafraîchissement de données (refreshRuntimeData), y compris pour un changement de donnée sans rapport avec ce panneau. Or une action "Modifier un élément → Rendre visible" ne modifie JAMAIS la base : c'est un changement DOM transitoire (style.visibility = ''). Quand un clic sur une ligne de Répéteur déclenche EN PARALLÈLE "Ouvrir la ligne cliquée" + "Rendre visible" + "Modifier une donnée", la branche "Modifier une donnée" est asynchrone (aller-retour serveur) et termine après les deux autres, synchrones. Son refreshRuntimeData() qui suit regénère alors "mail content" depuis son état par défaut, écrasant le "Rendre visible" qui venait tout juste d'être posé — le panneau redevient invisible. Un second clic sur le MÊME mail "corrige" l'affichage car la donnée est déjà à jour, donc la Condition ne redéclenche plus l'action de modification, plus de refresh, plus d'écrasement ; mais ouvrir un AUTRE mail reproduisait le même écrasement. Fix : avant de remplacer wrapper.innerHTML, sauvegarder le style inline, la classe et les data-* de chaque élément du sous-arbre, puis les réappliquer juste après la regénération — la résolution des {{champ}} reste correcte (c'est le but premier de la fonction) sans plus annuler les changements posés par une action "Modifier un élément" au même clic. Les trois actions du graphe (ouvrir la ligne, rendre visible, modifier la donnée) restent connectées telles quelles, sans aucun retrait. Co-Authored-By: Claude Sonnet 5 --- templates/play.html | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/templates/play.html b/templates/play.html index fb406600..ab635e79 100644 --- a/templates/play.html +++ b/templates/play.html @@ -342,6 +342,26 @@ screenData.elements.filter(function(e){ return !e.parent_id; }).forEach(function(el) { const wrapper = screenDiv.querySelector('.playElement[data-el-id="' + el.id + '"] .playElementInner'); if (!wrapper || !el.rendered_html || el.rendered_html.indexOf(PLACEHOLDER_OPEN) === -1) return; + + // Avant de regénérer ce sous-arbre depuis le HTML D'ORIGINE (nécessaire + // pour ne pas laisser les valeurs d'un mail précédent affichées, voir + // plus haut), sauvegarde l'état visuel TRANSITOIRE posé par une action + // "Modifier un élément" (jamais enregistré en base — voir + // applyElementProperty) sur cet élément ou l'un de ses descendants, + // pour le réappliquer après. Sans ça, un panneau rendu visible par + // cette action (ex. "mail content") redevenait invisible à CHAQUE + // rafraîchissement de données, y compris pour un changement de donnée + // sans rapport avec lui — puisque wrapper.innerHTML = html ci-dessous + // recrée entièrement ses nœuds à partir de leur état PAR DÉFAUT. + const savedState = {}; + wrapper.querySelectorAll('[data-element-id]').forEach(function(node) { + savedState[node.dataset.elementId] = { + style: node.getAttribute('style') || '', + className: node.className, + dataset: Object.assign({}, node.dataset), + }; + }); + let html = el.rendered_html; Object.keys(row).forEach(function(fieldName) { if (fieldName === 'id') return; @@ -351,6 +371,14 @@ html = html.split(PLACEHOLDER_OPEN + fieldName + PLACEHOLDER_CLOSE).join(tmp.innerHTML); }); wrapper.innerHTML = html; + + wrapper.querySelectorAll('[data-element-id]').forEach(function(node) { + const saved = savedState[node.dataset.elementId]; + if (!saved) return; + node.setAttribute('style', saved.style); + node.className = saved.className; + Object.assign(node.dataset, saved.dataset); + }); }); }