Désactiver cache JS et BootstrapWebPage / recharger une ressource HTML d'un objet externe

Bonjour

nous avons un objet externe qui affiche une BootstrapWebPage.
Cette dernière utilise des ressources JS et HTML (template Mustache).

public class BCSIRCMPublicPortalItem extends ExternalObject {
	private static final long serialVersionUID = 1L;

	private static String FUNCTION_ARCHITECTURE_API = "/bcsi/functions-architecture/v1/";

	/**
	 * Display method
	 * @param params Request parameters
	 */
	@Override
	public Object display(Parameters params) {
		BootstrapWebPage wp = new BootstrapWebPage(params.getRoot(), title, false);

		wp.appendHTML(MustacheTool.apply(HTMLTool.getResourceHTMLContent(this, "HTML_APPLICATIONS"), buildMustacheJSON(langSimplicite, itemData, textMap)));
		return wp.toString();
	}

Lors de développements, la moindre modification au template HTML provoque l’affichage de l’erreur suivante :

image

Il faut alors rouvrir la page en navigation privée pour que la page s’affiche correctement.

Comment paramétrer ma BootstrapWebPage pour lui dire qu’il n’y a pas de cache ?

Cordialement
Amandine T.

[Platform]
Status=OK
Version=4.0.P24
BuiltOn=2020-09-30 12:05 (revision 5a820e8be619728c3675ce2fc5d5beee3afe294a)
Encoding=UTF-8
EndpointIP=21.0.9.2
EndpointURL=http://383e0a627747:8080
TimeZone=Europe/Paris
SystemDate=2020-10-27 23:19:05

Cela ressemble à un pb de clear cache partiel (au sein de la session courante) pour la ressource HTML de votre objet externe. Ce n’est pas un pb de “cache” au sens du cache navigateur (qui ne concerne que les ressources JS et CSS) mais bien au sens cache serveur.

Je vais essayer de reproduire le pb décrit

PS: dans la demo sur le mode d’exemple frontend Mustache on utilise un pattern “mixte” compatible avec un affichage en zone publique (full page) ou au sein de la UI (responsive component): cf. GitHub - simplicitesoftware/module-demo-mustache: Simplicite + Mustache demo frontend module, cf notamment le code serveur de l’objet externe:
module-demo-mustache/src/com/simplicite/extobjects/DemoMustache/DemoMustacheFrontend.java at master · simplicitesoftware/module-demo-mustache · GitHub

Avec ce module en travaillant en dev au sein de la UI je ne constate pas de pb de cache quand je modifie à chaud le HTML du template mustache:

Avant


Je modifie le template Mustache:

Après

Mais il y a effectivement un pb du coté de la zone publique. On va investiguer. En tout cas le pattern ci-dessus permet de travailler en dev de manière “confortable”.

Merci @david pour votre retour.

J’ai mis en place tes précognitions. Par contre, j’ai quand même l’erreur suivante à chaque modification du template :
image

Voici un vrai cas,

package com.simplicite.extobjects.BCSIPublic_RCM;


import com.simplicite.util.AppLog;
import com.simplicite.util.ExternalObject;
import com.simplicite.util.ListOfValues;
import com.simplicite.util.tools.Parameters;
import com.simplicite.util.tools.HTMLTool;
import com.simplicite.util.tools.MustacheTool;

import java.lang.StringBuilder;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.json.JSONObject;
import org.json.JSONArray;

import com.simplicite.webapp.web.BootstrapWebPage;

/**
 * External object BCSIRCMPublicPortalItem
 */
public class BCSIRCMPublicPortalItem extends ExternalObject {
	private static final long serialVersionUID = 1L;

	private static String FUNCTION_ARCHITECTURE_API = "/bcsi/functions-architecture/v1/";

	/**
	 * Display method
	 * @param params Request parameters
	 */
	@Override
	public Object display(Parameters params) {
		String langSimplicite = "FRA";
		if (params.getParameters() != null && "EN".equalsIgnoreCase(params.getParameter("LANG"))) {
			langSimplicite = "ENU";
		}

		Map<String, String> textMap = new HashMap<String, String>(); // Code simplifié ici on récupère des textes statiques

		boolean noTarget = StringUtils.isBlank(params.getParameter("targetObject"));

		String html;
		String title;
		String initJsInstruction = null;
		JSONObject mustacheJSON = null;
		
		if (noTarget) {
			title = textMap.get("RCM_PORTAL");
			html = "<div id='error' class='alert alert-danger'>"  + textMap.get("ERROR_TARGET") + "</div>";
		} else {

			JSONObject itemData = getJSONObject(params);

			mustacheJSON = buildMustacheJSON(langSimplicite, itemData, textMap);

			if (StringUtils.isBlank(itemData.optString("ERROR"))) {
				if ("rootPublishedBusinessCapabilities".equals(params.getParameter("targetObject"))
							|| "publishedBusinessCapabilities".equals(params.getParameter("targetObject"))) {
					params.setParameter("targetObject", "businessCapabilities");
					title = textMap.get(params.getParameter("targetObject"));
					itemData.put("colorH", params.getParameter("colorH"));
					itemData.put("colorL", params.getParameter("colorL"));
				}

				html = HTMLTool.getResourceHTMLContent(this, "HTML_" + params.getParameter("targetObject").toUpperCase());
			} else {
				title = textMap.get("RCM_PORTAL");
				html = "<div id='error' class='alert alert-danger'>" + textMap.get("ERROR_DATA") + "</div>";
			}
		}
		
		BootstrapWebPage wp = new BootstrapWebPage(params.getRoot(), title, false);
		if (loadResource(params, wp) && mustacheJSON != null) {
			wp.setReady(getName() + ".init(" + mustacheJSON.toString() + ");");
		}
		wp.appendHTML(html);
		return wp.toString();
	}

	/** 
	 * Rajoute les CSS et scripts nécessaires pour afficher la page. 
	 * @return true : s'il y a un script de rendering à lancer.
	 **/
	private boolean loadResource(Parameters params, BootstrapWebPage wp) {
		boolean hasScriptToLaunch = false;
		setDecoration(false);

		wp.setFavicon(HTMLTool.getResourceIconURL(this, "FAVICON"));
		wp.appendCSSInclude(HTMLTool.getResourceCSSURL(this, "CSS_" + getEnv(params)));
		wp.setMenu("menu-brand", "<img id=\"logoRenault\" src=\"" + HTMLTool.getResourceImageURL(this, "LOGO") + "\"/>", null, true, false, true);
		
		if (StringUtils.isNotBlank(params.getParameter("targetObject"))) {
			
			String urlScript = HTMLTool.getResourceJSURL(this, "SCRIPT_" + params.getParameter("targetObject").toUpperCase());
			if (StringUtils.isNotBlank(urlScript)) {
				wp.appendJSInclude(urlScript);
				wp.appendMustache();
				hasScriptToLaunch = true;
			}
		}
	
		return hasScriptToLaunch;
	}

	private String getEnv(Parameters params) {
		if (params.getContextURL().contains("-dev")) {
			return "DEV";
		} else if (params.getContextURL().contains("-re7")) {
			return "UAT";
		}
		return "OPE";
	}

	private JSONObject getJSONObject(Parameters params) {
		return new JSONObject("{\"businessCapabilityBasemapId\":\"16\",\"hasPart\":[{\"businessCapabilityBasemapId\":\"16\",\"publishedBusinessCapabilityMotherName\":\"\",\"rootPublishedBusinessCapabilityName\":\"Sales & Operations\",\"hasPart\":[{\"businessCapabilityBasemapId\":\"16\",\"publishedBusinessCapabilityMotherName\":\"Manage Marketing Operations\",\"rootPublishedBusinessCapabilityName\":\"\",\"hasPart\":[],\"name\":\"Analyze Market Trends\",\"description\":\"\",\"rootPublishedBusinessCapabilityId\":\"\",\"businessCapabilityBasemapName\":\"B10\",\"publishedBusinessCapabilityMotherId\":\"3516\",\"rowId\":\"3517\"},{\"businessCapabilityBasemapId\":\"16\",\"publishedBusinessCapabilityMotherName\":\"Manage Marketing Operations\",\"rootPublishedBusinessCapabilityName\":\"\",\"hasPart\":[],\"name\":\"Define Market Segmentation\",\"description\":\"\",\"rootPublishedBusinessCapabilityId\":\"\",\"businessCapabilityBasemapName\":\"B10\",\"publishedBusinessCapabilityMotherId\":\"3516\",\"rowId\":\"3518\"},{\"businessCapabilityBasemapId\":\"16\",\"publishedBusinessCapabilityMotherName\":\"Manage Marketing Operations\",\"rootPublishedBusinessCapabilityName\":\"\",\"hasPart\":[],\"name\":\"Manage Campaigns\",\"description\":\"Omni-Channels\",\"rootPublishedBusinessCapabilityId\":\"\",\"businessCapabilityBasemapName\":\"B10\",\"publishedBusinessCapabilityMotherId\":\"3516\",\"rowId\":\"3519\"}],\"name\":\"Manage Marketing Operations\",\"description\":\"\",\"rootPublishedBusinessCapabilityId\":\"192\",\"businessCapabilityBasemapName\":\"B10\",\"publishedBusinessCapabilityMotherId\":\"\",\"rowId\":\"3516\"},],\"name\":\"Sales & Operations\",\"description\":\"test description1\",\"businessCapabilityBasemapName\":\"B10\",\"rowId\":\"192\"}");
	}
	

	/** Build a JSON Object for Mustache. It contains the item to show and a list of translations. */
	private JSONObject buildMustacheJSON(String langSimplicite, JSONObject itemData, Map<String, String> textMap) {
		JSONObject data = new JSONObject();
		data.put("isEnglish", "ENU".equals(langSimplicite));
		data.put("lang", "ENU".equals(langSimplicite) ? "EN" : "FR");

		data.put("translation", textMap);
		data.put("item", itemData);

		return data;
	}
}

Le HTML

<div id="page-rcm"><h3>Loading...</h3></div>

<template id="template">
	<h1>{{item.name}}</h1>
	<h1>Test2</h1>
	<p>{{item.description}}</p>
	<div id="col1" class="container col-md-3"></div>
	<div id="col2" class="container col-md-3 col-md-offset-1"></div>
	<div id="col3" class="container col-md-3 col-md-offset-1"></div>
</template>

et le JS

BCSIRCMPublicPortalItem = typeof BCSIRCMPublicPortalItem !== "undefined" ? BCSIRCMPublicPortalItem : (function($) {

	function init(mustacheJson) {
		try {
			console.log($('#template'));
			console.log($('#template').html());
			console.log($('#page-rcm'));
			if (typeof Mustache === 'undefined') throw 'Mustache not available';
			$('#page-rcm').html(Mustache.render($('#template').html(), mustacheJson));
		} catch(e) {
		console.log(e);
			console.error('Render error: ' + e.message);
		}

		var bcJson = mustacheJson.item;
		var businessCapabilities = bcJson["hasPart"];
		
		var totalChildrenPerCol;
		
		if (businessCapabilities.length <= 3) {
			totalChildrenPerCol = 1;
		} else {
			var totalChildren = 1;
			businessCapabilities.forEach(businessCapability => {
				if (businessCapability.hasPart.length !== 0)
					totalChildren += businessCapability.hasPart.length;
			});
			totalChildrenPerCol = totalChildren/3;
		}
		
		var indexActiveCol = 0;
		var countChildrenPerActiveCol = 0;
		
		businessCapabilities.forEach(businessCapability => {
			
	 		var parent = $("<div />", { 
	 			class : "row parent", 
	 			style : "background-color: hsl(" + bcJson.colorH + ", 100%, " + (parseInt(bcJson.colorL) + 10) + "%);", 
	 			on: { click: 
	 				function() { 
	 					window.open("/maps/businessCapabilityBasemap/levelNBusinessCapabilities/" + businessCapability.rowId + '?colorH=' + bcJson.colorH + '&colorL=' + (parseInt(bcJson.colorL) + 10), '_blank'); 
	 				}
	 			}
	 		});
	 		
	 		parent.append($("<div />", { class : "parent-text", text : businessCapability.name }));
	 		
	 		if (businessCapability.hasPart && businessCapability.hasPart.length > 0) {
	 			businessCapability.hasPart.forEach(bcChild => {
	 				var child = $("<div />", {
	 					class: "child col-md-10 col-md-offset-1", 
	 					style:"background-color: hsl(" + bcJson.colorH + ", 100%, " + (parseInt(bcJson.colorL) + 20) + "%);",
	 					on: { click: 
			 				function(e) { 
			 					e.stopPropagation();
			 					window.open("/maps/businessCapabilityBasemap/levelNBusinessCapabilities/" + bcChild.rowId + '?colorH=' + bcJson.colorH + '&colorL=' + (parseInt(bcJson.colorL) + 20), '_blank');
			 				}
			 			}
	 				});
	 				parent.append(child.append($("<div />", { class: "text", text: bcChild.name })));
	 			});
	 			
	 		}
	 		
	 		if (countChildrenPerActiveCol < totalChildrenPerCol) {
	 			if (businessCapability.hasPart.length === 0) countChildrenPerActiveCol++;
	 			else countChildrenPerActiveCol += businessCapability.hasPart.length;
	 		} else {
	 			indexActiveCol++;
	 			if (businessCapability.hasPart.length === 0) countChildrenPerActiveCol++;
	 			else countChildrenPerActiveCol = businessCapability.hasPart.length;
	 		}
	 		
	 		$("#col" + (indexActiveCol%3 + 1)).append(parent);

		});
	}
	
	return { init : init };
	
})(jQuery);

Toute modification du template provoque l’erreur ci-dessus. Il ne trouve plus les div template et page-rcm. Si j’ouvre la page en navigation privée, je n’ai pas d’erreur.
Repérez-vous si j’ai fait une erreur ?

Cordialement
Amandine T.

Il y a une anomalie loguée sur le rechargement dynamique des ressources HTML, je pense qu’on est ici dans ce cas. Je vais la faire remonter dans la pile car il me semble qu’elle n’a pas encore été investiguée/corrigée. J’ai reclassé le post en “Defect” pour ce sujet.

Le pb ne semble pas être un problème de cache HTML mais de div absente.
$(’#template’).html() est vide.

J’ai créé un objet externe avec une ressource HTML sans code complexe, elle est bien chargée et rafraichie en cas de modification sans vider le cache serveur ou navigateur.

Votre code charge le HTML puis l’ajoute à la page spécifiquement :
html = HTMLTool.getResourceHTMLContent(this, "HTML_" + params.getParameter("targetObject").toUpperCase());

C’est au niveau de votre wp.appendHTML(html) qu’il faut debugger ce que vous lui passez après vos traitements pour voir s’il y a bien <template id="template"> dedans quand l’erreur se produit, par exemple quand votre if (StringUtils.isBlank(itemData.optString("ERROR"))) est false… ou targetObject pas renseigné…

Sinon votre code front doit tester la présence du div avant de lancer le parser mustache.
if ($("#template").length) ...

NB: l’id de la div est un peu vague et général (il faudra un id plus spécifique pour mieux l’isoler)

Non reproduit de mon côté.

Par contre ajouter le HTML par code alors qu’il est déjà intégré à la page par Simplicité s’il est déclaré comme ressource de l’objet externe me semble redondant, voir contradictoire.

Le cas ici c’est bien le HTML sous forme de ressource d’objet externe de type HTML et en zone publique

Ca change tout. Un clear-cache partiel n’a pas à rafraichir par construction toutes les sessions en mémoire mais que le core-cache et la session du designer. Si certaines ressources sont rechargées pour “tout le monde” en session c’est qu’elles passent sûrement par d’autres mécanismes de cache ou de compilation.

On va ajouter un rechargement des ressources pour le user “public” lors d’un clear-cache partiel, mais ce n’est pas généralisable à tous les Users ou API, en tout cas pas un defect mais un nouveau besoin de clear-cache partiel du user “public” en même temps que la session du designer.