Ne régénère plus que l'élément concerné par "Ouvrir la ligne cliquée"
Cause du "rechargement" perçu par l'utilisateur (et du flash à vide sur
la ligne de Répéteur juste cliquée, visible sur une vidéo de repro) :
applyOpenRowBindings() ne pouvait cibler que les éléments de PREMIER
NIVEAU de l'écran (seuls eux ont un wrapper .playElement dans le DOM).
Sur cet écran, "mail content" est niché à 2 conteneurs de profondeur, et
le SEUL élément de premier niveau est le conteneur racine de tout
l'écran — donc chaque clic sur une ligne de Répéteur régénérait
littéralement tout l'écran (jauges, onglets, Répéteur compris) pour ne
mettre à jour qu'un seul panneau de détail, avec un flash à vide pendant
la reconstruction.
Fix : applyOpenRowBindings() cible maintenant directement, à n'importe
quel niveau d'imbrication, le(s) élément(s) qui portent réellement un
{{champ}} non résolu (repéré via document.querySelector
('[data-element-id=...]'), disponible sur CHAQUE élément rendu, pas
seulement les élément de premier niveau) — et seulement les plus "hauts"
parmi eux, pour ne jamais régénérer un même nœud deux fois. Seul "mail
content" est donc désormais remplacé (via replaceWith), sans toucher au
Répéteur ni au reste de l'écran. La sauvegarde/restauration de l'état
visuel transitoire (style, classes, dataset hors clickBound/hoverBound/
hoverTriggerBound) suit le même principe, appliquée au nœud remplacé et
à ses descendants.
Aucun aller-retour réseau n'a jamais eu lieu ici (refreshRuntimeData()
utilise déjà fetch/JSON, pas de navigation de page) — la sensation de
rechargement venait uniquement de la granularité du remplacement DOM,
pas d'un manque d'AJAX.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
5114372cc4
commit
20adfa4169
+53
-16
@@ -339,30 +339,63 @@
|
|||||||
const screenData = screensData.find(s => s.id === window.currentScreenId);
|
const screenData = screensData.find(s => s.id === window.currentScreenId);
|
||||||
const screenDiv = document.querySelector('.playScreen[data-screen-id="' + window.currentScreenId + '"]');
|
const screenDiv = document.querySelector('.playScreen[data-screen-id="' + window.currentScreenId + '"]');
|
||||||
if (!row || !screenData || !screenDiv) return;
|
if (!row || !screenData || !screenDiv) return;
|
||||||
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
|
// Cible directement, à N'IMPORTE QUEL niveau d'imbrication, le ou les
|
||||||
// pour ne pas laisser les valeurs d'un mail précédent affichées, voir
|
// éléments qui portent vraiment un {{champ}} non résolu — et non plus
|
||||||
// plus haut), sauvegarde l'état visuel TRANSITOIRE posé par une action
|
// seulement les éléments de PREMIER NIVEAU de l'écran. Avant ce
|
||||||
|
// correctif, un panneau de détail niché à 2-3 niveaux (ex. "mail
|
||||||
|
// content" posé dans un conteneur, lui-même dans un autre) forçait à
|
||||||
|
// regénérer TOUT son ancêtre de premier niveau — potentiellement
|
||||||
|
// l'écran entier (jauges, onglets, Répéteur compris) s'il n'y avait
|
||||||
|
// qu'un seul gros conteneur racine — d'où l'impression trompeuse d'un
|
||||||
|
// rechargement de page (flash à vide) à chaque clic sur une ligne.
|
||||||
|
//
|
||||||
|
// Parmi les éléments qui ont un {{champ}} non résolu, on ne garde que
|
||||||
|
// les plus "hauts" dans l'arbre : si un ancêtre ET un de ses
|
||||||
|
// descendants sont tous deux candidats, seul l'ancêtre est régénéré
|
||||||
|
// (son rendered_html inclut déjà ce descendant) — sans ce filtre, le
|
||||||
|
// descendant serait régénéré deux fois, la 2e fois à partir du DOM déjà
|
||||||
|
// à jour de la 1re passe.
|
||||||
|
const byId = {};
|
||||||
|
screenData.elements.forEach(function(e){ byId[e.id] = e; });
|
||||||
|
function hasUnresolvedPlaceholder(e) {
|
||||||
|
return !!(e.rendered_html && e.rendered_html.indexOf(PLACEHOLDER_OPEN) !== -1);
|
||||||
|
}
|
||||||
|
function hasCandidateAncestor(e) {
|
||||||
|
let p = e.parent_id ? byId[e.parent_id] : null;
|
||||||
|
while (p) {
|
||||||
|
if (hasUnresolvedPlaceholder(p)) return true;
|
||||||
|
p = p.parent_id ? byId[p.parent_id] : null;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const targets = screenData.elements.filter(function(e) {
|
||||||
|
return hasUnresolvedPlaceholder(e) && !hasCandidateAncestor(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
targets.forEach(function(el) {
|
||||||
|
const target = screenDiv.querySelector('[data-element-id="' + el.id + '"]');
|
||||||
|
if (!target) return;
|
||||||
|
|
||||||
|
// Avant de regénérer ce nœud 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
|
// "Modifier un élément" (jamais enregistré en base — voir
|
||||||
// applyElementProperty) sur cet élément ou l'un de ses descendants,
|
// applyElementProperty) sur ce nœud ou l'un de ses descendants, pour
|
||||||
// pour le réappliquer après. Sans ça, un panneau rendu visible par
|
// le réappliquer après. Sans ça, un panneau rendu visible par cette
|
||||||
// cette action (ex. "mail content") redevenait invisible à CHAQUE
|
// action (ex. "mail content") redevenait invisible à CHAQUE
|
||||||
// rafraîchissement de données, y compris pour un changement de donnée
|
// rafraîchissement de données, y compris pour un changement de donnée
|
||||||
// sans rapport avec lui — puisque wrapper.innerHTML = html ci-dessous
|
// sans rapport avec lui — puisque le remplacement ci-dessous recrée
|
||||||
// recrée entièrement ses nœuds à partir de leur état PAR DÉFAUT.
|
// entièrement ses nœuds à partir de leur état PAR DÉFAUT.
|
||||||
const savedState = {};
|
const savedState = {};
|
||||||
wrapper.querySelectorAll('[data-element-id]').forEach(function(node) {
|
[target].concat(Array.from(target.querySelectorAll('[data-element-id]'))).forEach(function(node) {
|
||||||
// clickBound/hoverBound/hoverTriggerBound (voir bindClicks/
|
// clickBound/hoverBound/hoverTriggerBound (voir bindClicks/
|
||||||
// bindHoverTexts/bindHoverTriggers) sont des indicateurs INTERNES du
|
// bindHoverTexts/bindHoverTriggers) sont des indicateurs INTERNES du
|
||||||
// moteur, jamais un état posé par une action "Modifier un élément" —
|
// moteur, jamais un état posé par une action "Modifier un élément" —
|
||||||
// les restaurer sur le nœud tout neuf créé ci-dessous lui ferait
|
// les restaurer sur le nœud tout neuf créé ci-dessous lui ferait
|
||||||
// croire à tort qu'un écouteur de clic/survol lui est déjà rattaché
|
// croire à tort qu'un écouteur de clic/survol lui est déjà rattaché
|
||||||
// (alors qu'aucun ne l'est réellement), ce qui bloquait
|
// (alors qu'aucun ne l'est réellement), ce qui bloquait
|
||||||
// silencieusement TOUS les clics suivants sur cet élément (ex. les
|
// silencieusement TOUS les clics suivants sur cet élément.
|
||||||
// lignes d'un Répéteur voisin, une fois régénérées avec lui).
|
|
||||||
const dataset = Object.assign({}, node.dataset);
|
const dataset = Object.assign({}, node.dataset);
|
||||||
delete dataset.clickBound;
|
delete dataset.clickBound;
|
||||||
delete dataset.hoverBound;
|
delete dataset.hoverBound;
|
||||||
@@ -382,9 +415,13 @@
|
|||||||
tmp.textContent = value;
|
tmp.textContent = value;
|
||||||
html = html.split(PLACEHOLDER_OPEN + fieldName + PLACEHOLDER_CLOSE).join(tmp.innerHTML);
|
html = html.split(PLACEHOLDER_OPEN + fieldName + PLACEHOLDER_CLOSE).join(tmp.innerHTML);
|
||||||
});
|
});
|
||||||
wrapper.innerHTML = html;
|
const container = document.createElement('div');
|
||||||
|
container.innerHTML = html;
|
||||||
|
const newNode = container.firstElementChild;
|
||||||
|
if (!newNode) return;
|
||||||
|
target.replaceWith(newNode);
|
||||||
|
|
||||||
wrapper.querySelectorAll('[data-element-id]').forEach(function(node) {
|
[newNode].concat(Array.from(newNode.querySelectorAll('[data-element-id]'))).forEach(function(node) {
|
||||||
const saved = savedState[node.dataset.elementId];
|
const saved = savedState[node.dataset.elementId];
|
||||||
if (!saved) return;
|
if (!saved) return;
|
||||||
node.setAttribute('style', saved.style);
|
node.setAttribute('style', saved.style);
|
||||||
|
|||||||
Reference in New Issue
Block a user