Bonjour Simon,
Merci encore pour ton retour.
Nous avons poursuivi les tests sur la démo autour de ce sujet.
À ce stade, ce que nous avons mis en place est le suivant :
- déclenchement d’une action asynchrone au chargement du formulaire côté front ;
- exécution du traitement côté back ;
- simulation d’un traitement externe dans la démo ;
- blocage d’une zone d’attribut et déblocage à la fin du traitement
- mise à jour d’un attribut à la fin du job (dans notre cas, la date du jour) ;
- affichage du tracker pendant l’exécution ;
- test d’un rechargement du formulaire à la fin pour refléter la nouvelle valeur ;
- ajout d’un garde côté front pour éviter de relancer automatiquement le traitement après reload.
Le fonctionnement async en lui-même est OK.
Le blocage se situe surtout à la fin du tracker : nous voulons simplement mettre à jour la valeur affichée dans le formulaire, sans que cela ne relance automatiquement le tracker ou l’action async au rechargement du form.
Nous avons réussi à contourner cela avec un garde côté front, mais cela ressemble davantage à un workaround qu’à un pattern standard car le user ne peut le faire que une fois par session chargé…
Existe-t-il dans Simplicité une manière recommandée de rafraîchir proprement un attribut ou une zone du formulaire à la fin d’un traitement async, sans relancer le traitement updateduform en boucle ? Ou bien autre chose sur laquelle le lancement du tracker au chargement du formulaire peut se conditionner ?
Voici les classes et méthodes testé sur DemoOrder :
ACTION_ASYNCH_UPDATE_FORM.xml (2.6 KB)
DemoOrder Java
public class DemoOrder extends ObjectDB {
private static final long serialVersionUID = 1L;
@Override
public boolean isActionEnable(String[] row, String action) {
if ("ACTION_ASYNCH_UPDATE_FORM".equals(action)) {
String status = getFieldValue("demoOrdStatus");
boolean enabled = "P".equals(status);
return enabled;
}
return true;
}
@BusinessObjectAction
public String updateFormArea(Action action, AsyncTracker tracker) {
setParameter("LOCK_FORM_RELOAD",false);
final String lang = getGrant().getLang();
final boolean isFrench = !lang.equals("ENU");
final String demoId = getRowId();
final String status = getFieldValue("demoOrdStatus");
if (!"P".equals(status)) {
return "SKIP: status != P";
}
if (tracker.isRunning()) return null;
// 2) Init tracker
tracker.setMinified(true);
tracker.setCloseable(true);
tracker.setMinifiable(true);
tracker.setStoppable(true);
tracker.setProgress(0);
tracker.start();
tracker.add("Début de synchro");
tracker.message("Initialisation...");
// 3) Choix scénario (simple, déterministe, sans paramètre)
final int pick = Math.abs((demoId != null ? demoId.hashCode() : 0) % 3);
final String scenario = pick == 0 ? "SUCCESS_3S" : (pick == 1 ? "SUCCESS_10S" : "ERROR");
JobQueue.push(
"updateFormAreaJob",
() -> {
ObjectDB testDemo = getGrant().getTmpObject(this.getName());
synchronized (testDemo.getLock()) {
try {
String dateNow = Tool.getCurrentDatetime();
// testDemo.resetValues(true, ObjectField.DEFAULT_ROW_ID);
testDemo.select(demoId);
testDemo.setFieldValue("demoOrdComments", dateNow);
testDemo.getTool().validateAndSave();
AppLog.info(
getClass(),
"ZAZA",
"DemoOrder mis à jour pour l'ID: " + demoId,
getGrant());
} catch (ValidateException | SaveException e) {
AppLog.error(
getClass(),
"postSave",
"Erreur lors de la sauvegarde de l'adaptation/version: "
+ e.getMessage(),
null,
getGrant());
}
}
try {
tracker.message("Synchronisation en cours...");
tracker.setProgress(10);
if ("SUCCESS_3S".equals(scenario)) {
// Succès en ~3s
Thread.sleep(1000);
tracker.add("Récupération des données...");
tracker.setProgress(50);
Thread.sleep(1000);
tracker.add("Mise à jour du formulaire...");
tracker.setProgress(80);
Thread.sleep(1000);
tracker.add("Synchronisation terminée (3s)");
tracker.setProgress(100);
// } else if ("SUCCESS_10S".equals(scenario)) {
} else {
// Succès en ~10s
Thread.sleep(2000);
tracker.add("Récupération des données...");
tracker.setProgress(30);
Thread.sleep(3000);
tracker.add("Téléchargement des documents...");
tracker.setProgress(60);
Thread.sleep(3000);
tracker.add("Mise à jour du formulaire...");
tracker.setProgress(90);
Thread.sleep(2000);
tracker.add("Synchronisation terminée (10s)");
tracker.setProgress(100);
}
// else {
// // Échec
// Thread.sleep(2000);
// tracker.add("Erreur: service de synchro indisponible");
// tracker.error("La synchronisation n'est pas disponible pour le
// moment. Veuillez réessayer plus tard.");
// }
} catch (Exception e) {
tracker.error("Erreur technique: " + e.getMessage());
AppLog.error(
getClass(),
"updateFormAreaJob",
"ERROR row_id=" + demoId,
e,
getGrant());
} finally {
tracker.stop();
AppLog.info(
getClass(),
"updateFormArea",
"END row_id=" + demoId + " scenario=" + scenario,
getGrant());
}
});
return null; // async => UI non bloquante
}
// code existant suite....
/** Quantity field name */
private static final String QUANTITY_FIELDNAME = "demoOrdQuantity";
}
DemoOrder JS Hook
/**
* Order business object
* @class
*/
Simplicite.UI.BusinessObjects.DemoOrder = class extends Simplicite.UI.BusinessObject {
/** @override */
onLoadForm(ctn, obj, p) {
super.onLoadForm(ctn, obj, p);
// Note that this client-side logic will be overridden
// anyway by server-side logic at save
const pup = $ui.getUIField(ctn, obj, 'demoOrdPrdId.demoPrdUnitPrice').ui;
const up = $ui.getUIField(ctn, obj, 'demoOrdUnitPrice').ui;
const q = $ui.getUIField(ctn, obj, 'demoOrdQuantity').ui;
const t = $ui.getUIField(ctn, obj, 'demoOrdTotal').ui;
const v = $ui.getUIField(ctn, obj, 'demoOrdVAT').ui;
const calcTotal = () => {
t.val(q.val() * up.val());
v.val(t.val() * parseFloat($grant.sysparams.DEMO_VAT) / 100);
};
// Change unit price if product is changed
pup.on('change', () => {
up.val(pup.val());
calcTotal();
});
// recalculate total if quantity changes
q.on('change', calcTotal);
const setAreaReadOnly = (areaEl, ro) => {
if (!areaEl) return;
// Visuel : grisé + pas de clic
areaEl.classList.toggle("lbc-sync-ro", ro);
// Fonctionnel : disable tous les champs éditables
const selectors = "input, textarea, select, button";
areaEl.querySelectorAll(selectors).forEach((el) => {
// on évite de casser les readonly natifs déjà présents
if (ro) {
el.dataset.prevDisabled = el.disabled ? "1" : "0";
el.disabled = true;
} else {
if (el.dataset.prevDisabled === "0") el.disabled = false;
delete el.dataset.prevDisabled;
}
});
};
const ensureSyncCss = () => {
if (document.getElementById("lbc-sync-css")) return;
const style = document.createElement("style");
style.id = "lbc-sync-css";
style.textContent = `
.lbc-sync-ro {
opacity: .65;
filter: grayscale(.15);
pointer-events: none; /* empêche clic/slider */
}
`;
document.head.appendChild(style);
};
ensureSyncCss();
// ===============================
// ASYNC ACTION AUTO-START (clean)
// ===============================
const act = "ACTION_ASYNCH_UPDATE_FORM";
const rowId = obj.getRowId();
if (!rowId) {
console.log("No rowId yet (creation mode) → async not started");
return;
}
const bo = $ui.getAjax().getBusinessObject("DemoOrder");
// FRONT GUARD
const statusField = $ui.getUIField(ctn, obj, "demoOrdStatus");
const status = statusField && statusField.ui ? statusField.ui.val()
: obj.getFieldValue("demoOrdStatus");
console.log("Status at load =", status);
if (status !== "P") {
console.log("Sync NOT started (status != P)");
return;
}
const syncKey = `demo-order-sync-${rowId}`;
const syncState = sessionStorage.getItem(syncKey);
if (syncState === "running") {
console.log("Sync already running for this record");
return;
}
if (syncState === "done") {
console.log("Sync already done for this record in this session");
return;
}
console.log("Starting async action:", act, "rowId=", rowId);
sessionStorage.setItem(syncKey, "running");
(async () => {
const area4 = ctn.find("#area4")[0] || document.getElementById("area4");
const area5 = ctn.find("#area5")[0] || document.getElementById("area5");
setAreaReadOnly(area4, true);
setAreaReadOnly(area5, true);
try {
const startRes = await bo.action(act, {
values: {
row_id: rowId
}
});
console.log("Async action started:", startRes);
if (!startRes || !startRes.tracker) {
console.warn("No tracker returned by async action");
sessionStorage.removeItem(syncKey);
setAreaReadOnly(area4, false);
setAreaReadOnly(area5, false);
return;
}
let finished = false;
$ui.view.form.tracker(startRes.tracker, {
title: "Auto Synchronisation",
progress: function(cbk) {
bo.action(act, {
values: {
row_id: rowId
},
track: "status"
})
.then((st) => {
console.log("Tracker status =", st);
// Etat terminal = T
if (!finished && st && st.state === "T") {
finished = true;
setAreaReadOnly(area4, false);
setAreaReadOnly(area5, false);
sessionStorage.setItem(syncKey, "done");
setTimeout(() => {
$ui.reloadForm();
}, 300);
}
// En cas d'erreur terminale éventuelle
if (!finished && st && st.state === "E") {
finished = true;
setAreaReadOnly(area4, false);
setAreaReadOnly(area5, false);
sessionStorage.removeItem(syncKey);
}
cbk(st);
})
.catch((e) => {
console.error("Tracker progress error:", e);
if (!finished) {
finished = true;
sessionStorage.removeItem(syncKey);
setAreaReadOnly(area4, false);
setAreaReadOnly(area5, false);
}
cbk({ state: "E" });
});
}
});
} catch (e) {
console.error("Start async failed:", e);
sessionStorage.removeItem(syncKey);
setAreaReadOnly(area4, false);
setAreaReadOnly(area5, false);
}
})();
}
};
Merci d’avance pour vos éclairages. 