1. Présentation
Ajax-Refresh est une API qui permet de rafraichir très simplement une partie de la page Web grâce à la technologie AJAX. Elle peut notamment être utilisée pour rafraîchir le contenu d'une portlet suite à une action de l'utilisateur.
Exemples d'usage :
- Changement de page dans une liste paginée de résultats ;
- Changement de mois dans le calendrier ;
- Changement d'onglets ;
- Ajout d'une tâche dans la portlet Gestion de tâches ;
- ...
L'API Ajax-Refresh offre de nombreux avantages :
- Simplicité : la mise en oeuvre d'Ajax-Refresh est très simple et ne nécessite pas de connaissance en programmation JavaScript ;
- Gains de productivité : le programmeur ne se préoccupe que du code métier qui le concerne, ce qui simplifie son travail ;
- Qualité du code : la simplification du code qu'entraîne l'utilisation de Ajax-Refresh permet de le rendre plus lisible et donc de meilleure qualité ;
- Maintenance aisée : grâce à un couplage très faible avec cette API, les jsp peuvent être maintenus plus facilement. Elle peut être appliquée facilement sur des portlets ou des JSP existants ;
- Compatibilité : Ajax-Refresh est compatible avec les critères d'accessibilité et de référencement (SEO) ;
- Complétude : l'API prend en charge l'ensemble de la problématique liée au rafraichissement partielle : préservation du contexte, gestion de l'historique de navigation, réinjection des JavaScript et des CSS, ...
L'API Ajax-Refresh est disponible à partir de JCMS 6.0.0 néanmoins les exemples de cet article ont été testés avec JCMS 6.0.1.
2. Principes
ajax-refresh-div dans laquelle des actions sont déclenchées par des liens ou des boutons ayant la classe CSS ajax-refresh. Le contenu de la balise DIV est alors remplacé avec le code HTML produit par la JSP appelée.2.1 Utilisation basique
<% jcmsContext.addJavaScript("js/jalios/ajax-refresh.js"); %>
<div class="ajax-refresh-div">
<%= new Date() %><br/>
<a class="ajax-refresh" href="example1.jsp">Refresh</a>
</div>
2.2 Rafraichissement de Portlet
L'utilisation de l'API Ajax-Refresh avec les portlets JCMS est encore plus simple car la mécanique du portail prend en charge pour toute portlet :
- l'inclusion du fichier
js/jalios/ajax-refresh.js - l'ajout d'une balise
<div>englobant ayant la classeajax-refresh-div.
Lors du rafraichissement, c'est la portlet qui est entièrement rafraichit, habillage compris.
2.3 Utilisations avancées
En complément, l'API fournit permet des utilisations avancées mises en oeuvre dans la suite de ce document.
- Demande de confirmation avant rafraichissement ;
- Chargement retardée
- Rafraichissement sans historique de navigation
- Rafraichissement explicite de Portlet
3 Fonctionnalités
3.1 Préservation du contexte
Quand une portlet est rafraichie par Ajax-Refresh, toutes les variables d'environnement (url, portail, catégories, membre authentifié, ...) sont positionnées afin de donner l'impression au gabarit d'affichage qu'il est dans un portail "virtuel". Le même gabarit JSP peut donc être utilisé dans les 2 contextes:
- requête normal
- requête ajax
D'un point de vue technique, lors du rafraîchissement, les variables contextuels (loggedMember, userLang, workspace, portalCategory, portal, ...) sont disponibles. Les méthodes de l'API J2EE (getQueryString(), getParameter(), ...) de HttpServletRequest sont peuvent être utilisées.

Limite
Les objets positionnés en attribut de requête (request.setAttribute()) ne sont pas accessibles dans les requêtes Ajax-Refresh. Pour propager un objet entre la requête initiale et les requêtes AJAX, utilisez:
- la session (
session.setAttribute()) ou - le contexte jcms (
jcmsContext.setAjaxRequestAttribute())
3.2 Inclusion des JavaScript et des CSS
Les fichiers JavaScript et CSS inclus lors d'une requête Ajax-Refresh avec les methodes jcmsContext.addJavaScript(), jcmsContext.addCSSHeader() et le tag <jalios:javascript> sont réinjectés dans la page portail d'origine.
Exemple
L'ajout d'une nouvelle portlet en mode portail charge automatiquement les ressources nécessaires (Javascript et CSS)
Limite
L'évènement onload n'est pas déclenché lors de ce type d'injection.
Note
Si la JSP ajaxifié n'est pas un gabarit de Portlet mais une simple JSP déclarant la DIV ajax-refresh-div. Celle-ci doit aussi inclure à la fin <%@ include file='/jcore/doAjaxFooter.jspf' %> afin de gérer les déclaration Javascript et CSS.
3.3 Actions de portlet
La mécanique des actions de portlet est compatible avec Ajax-Refresh.
Pour rappel, cette mécanique permet:
- de transmettre des paramètres à des portlets mises en cache.
- de construire des URL contenant des paramètres qui n'apparaîtront pas dans la QueryString.
- de conserver des paramètres en session.
Exemple
Les actions de portlet sont utilisées par la Portlet Calendrier pour effectuer un changement de mois (en invalidant les caches et en mémorisant le nouveau mois à afficher).
3.4 Gestion de l'historique de navigation
Les requêtes Ajax-Refresh sont ajoutées à l'historique du navigateur. Lors d'un changement de page la requête "précédente" ou "suivante" est rejouée.
Les requêtes POST et celles ayant la classe CSS ajax-action ne sont pas historisées.
Limites
Lors d'un changement d'historique les modifications JavaScript du DOM ne sont pas conservées. Contournement:
- le navigateur ré exécute le code JavaScript
- la dernière requête Ajax-Refresh est rejouée
3.5 Accessibilité et référencement
3.6 Limitations
3.6.1 Appel de méthodes distantes
La technologie Ajax-Reresh rafraichit l'ensemble d'une Portlet. Dans les autres cas il faut utiliser JSON/RPC. La technologie JSON/RPC permet d'appeler depuis du code JavaScript des méthodes Java. C'est le choix qui a été fait pour les menus contextuels.
3.6.2 Cross Site Scripting
Pour des raisons de sécurité, les requêtes Ajax doivent interroger le serveur ayant fournit le code JavaScript. Il n'est donc pas possible d'inclure dans un portail JCMS une portlet provenant d'un autre serveur JCMS.
4. Mise en oeuvre
4.1 Portlet JSP
Pour les premiers exemples, le développement se fera dans le gabarit d'une Portlet JSP. En enlevant la classe ajax-refresh, vous pouvez tester le comportement avec rechargement complet de la page.
4.1.1 Rafraichîssement de portlet
Dans l'exemple ci-dessous, lors d'un clic sur le lien "Refresh" la portlet est rafraichie et affiche la date et l'heure courante. Si le code JavaScript est désactivé, le lien reste cliquable et rafraichit toute la page.
<%@ include file="/jcore/doInitPage.jsp" %>
<%@ include file='/jcore/portal/doPortletParams.jsp' %>
<h3>Hello World !</h3>
<p>Date: <%= new Date() %></p>
<a class="ajax-refresh" href="<%= ServletUtil.getUrl(request) %>">Refresh</a>
Note
Lors d'un clic sur le bouton "back", la date affichée correspond à la date actuelle et non la précédente, car la requête est rejouée.
4.1.2 Rafraichîssement avec paramètres
Une évolution de l'exemple précédent en introduisant un paramètre count passé dans l'URL. Le tag <jalios:javascript> permet l'exécution de JavaScript pour afficher ce paramètre. La classe CSS confirm demande une confirmation à l'utilisateur avant de suivre le lien.
<%@ include file="/jcore/doInitPage.jsp" %>
<%@ include file='/jcore/portal/doPortletParams.jsp' %>
<%
int count = Util.toInt(request.getParameter("count"),0);
String url = ServletUtil.getUrlWithUpdatedParam(request,"count", String.valueOf(count+1));
%>
<h3>Hello World !</h3>
<p>Count: <%= count %></p>
<a class="ajax-refresh confirm" href="<%= url %>">Refresh</a>
<jalios:javascript>
alert("Count: <%= count %>");
</jalios:javascript>
Dans ce cas, le bouton "back" rejouant la précédente requête affichera correctement la valeur précédente (count-1).
4.1.3 Rafraichîssement depuis un formulaire
ajax-refresh est placé sur le bouton de soumission. Le texte affiché par la portlet contient le nom saisie par l'utilisateur et le titre du portail courant (qui est préservé dans le contexte Ajax).<%@ include file="/jcore/doInitPage.jsp" %>
<%@ include file='/jcore/portal/doPortletParams.jsp' %>
<% String user = Util.getString(request.getParameter("user"),""); %>
<h3>Welcome <%= user %> to <%= portal.getTitle(userLang) %>!</h3>
<form action="<%= ServletUtil.getResourcePath(request) %>" method="POST">
<label for="user">Nom: </label><br/>
<input name="user" value="<%= user %>" /><br/>
<input type="submit" name="opSubmit" value="Refresh" class="ajax-refresh"/>
</form>
4.2 Portlet Requête/Itération
4.2.1 Gabarit simple
L'exemple ci-dessous est le squelette du gabarit d'une Portlet Requête/Itération. Il n'y a pas d'Ajax-Refresh.
<%@ include file='/jcore/doInitPage.jsp' %> <%@ include file='/jcore/portal/doPortletParams.jsp' %> <% PortletQueryForeach box = (PortletQueryForeach) portlet; %> <%@ include file='doQuery.jsp' %><%@ include file='doSort.jsp' %> <ul> <%@ include file='doForeachHeader.jsp' %> <li><jalios:link data='<%= itPub %>'/></li> <%@ include file='doForeachFooter.jsp' %> </ul> <%@ include file='doPager.jsp' %>
4.2.2 Chargement différé
Lors de l'affichage d'une page portail, toutes les portlets sont rendues. Certaines portlets peuvent effectuer de long traitement pénalisant le chargement de la page. C'est par exemple le cas, pour les portlets interrogeant des systèmes externes (web services, base de données, ...)
Une solution consiste à chargées le contenu de ces portlets de manière différée, après le rendu de la page.
Au premier chargement de la page, la portlet produit un message d'attente. Puis cette portlet est réinterrogée par Ajax-Refresh une fois la page rendue. Elle fait alors son traitement complet et renvoie le résultat attendu.
La méthode jcmsContext.isAjaxRequest() permet de savoir si la portlet est interrogée en Ajax, c'est-à-dire à la seconde étape. Le contenu ne sera généré que dans ce cas.
Pour fonctionner, la skin de la Portlet doit avoir la classe ajax-lazy. Ceci n'est pas géré en standard, et le gabarit de la skin doit être modifié manuellement.
<%@ include file='/jcore/doInitPage.jsp' %>
<%@ include file='/jcore/portal/doPortletParams.jsp' %>
<% PortletQueryForeach box = (PortletQueryForeach) portlet; %>
<% if (!jcmsContext.isAjaxRequest()) { %>
Chargement en cours...
<% } else { %>
<%@ include file='doQuery.jsp' %>
<%@ include file='doSort.jsp' %>
<ul>
<%@ include file='doForeachHeader.jsp' %>
<li><jalios:link data='<%= itPub %>'/></li>
<%@ include file='doForeachFooter.jsp' %>
</ul>
<%@ include file='doPager.jsp' %>
<% } %>
Note
Cette technique est à réserver pour des cas exceptionnels. Il faut chercher à optimiser le temps de traitement des portlet notamment en utilisant les caches de JCMS.
4.2.3 Critères de tri
Dans cet exemple, l'ensemble des résultats construit par la portlet Requete/Itération est trié par un comparateur en fonction d'un paramètre passé dans l'URL.
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ include file='/jcore/doInitPage.jsp' %>
<%@ include file='/jcore/portal/doPortletParams.jsp' %>
<% PortletQueryForeach box = (PortletQueryForeach) portlet; %>
<%@ include file='doQuery.jsp' %>
<%@ include file='doSort.jsp' %>
<%
String order = Util.getString(request.getParameter("order"),"");
Comparator comp = Publication.getComparator(order, false);
if (comp != null){
TreeSet tmp = new TreeSet(comp);
tmp.addAll(collection);
collection = tmp;
}
String urlTitle = ServletUtil.getUrlWithUpdatedParam(request,"order", "title");
String urlCDate = ServletUtil.getUrlWithUpdatedParam(request,"order", "cdate");
String urlPDate = ServletUtil.getUrlWithUpdatedParam(request,"order", "pdate");
%>
<p>
Trier par
<a href="<%= urlTitle %>" class="ajax-refresh">Titre</a> -
<a href="<%= urlCDate %>" class="ajax-refresh">CDate</a> -
<a href="<%= urlPDate %>" class="ajax-refresh">PDate</a>
</p>
<ul>
<%@ include file='doForeachHeader.jsp' %>
<li><jalios:link data='<%= itPub %>'/></li>
<%@ include file='doForeachFooter.jsp' %>
</ul>
<%@ include file='doPager.jsp' %>
Note
Cet exemple est pédagogique. Il existe des outils plus simple pour réaliser cette fonctionnalité (cf. Tag <jalios:pager>).
4.2.4 Critères de tri avec Actions de portlet
La version 5 de JCMS à introduit la notion d'action de Portlet. Cette fonctionnalité est compatible avec Ajax-Refresh.
Dans l'exemple ci-dessous, le paramètre order est maintenant construit avec des actions de Portlet et sauvegardé en session.
Les methodes sendAction(), receiveAction() et getActionParam() de la classe PortalManager permettent l'usage des actions de portlet.
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ include file='/jcore/doInitPage.jsp' %>
<%@ include file='/jcore/portal/doPortletParams.jsp' %>
<% PortletQueryForeach box = (PortletQueryForeach) portlet; %>
<%@ include file='doQuery.jsp' %>
<%@ include file='doSort.jsp' %>
<%
String order = Util.getString(PortalManager.receiveAction(request, box, "order"),"");
Comparator comp = Publication.getComparator(order, false);
if (comp != null){
TreeSet tmp = new TreeSet(comp);
tmp.addAll(collection);
collection = tmp;
}
String urlTitle = PortalManager.sendAction(request , box, "order", "title");
String urlCDate = PortalManager.sendAction(request , box, "order", "cdate");
String urlPDate = PortalManager.sendAction(request , box, "order", "pdate");
%>
<p>
Critères de tri:
<a href="<%= urlTitle %>" class="ajax-refresh">Titre</a> -
<a href="<%= urlCDate %>" class="ajax-refresh">CDate</a> -
<a href="<%= urlPDate %>" class="ajax-refresh">PDate</a>
</p>
<ul>
<%@ include file='doForeachHeader.jsp' %>
<li><jalios:link data='<%= itPub %>'/></li>
<%@ include file='doForeachFooter.jsp' %>
</ul>
<%@ include file='doPager.jsp' %>
4.2.5 Ajout de contenu
Cet exemple propose un formulaire de création de contenu traité par un JavaBean. Il peut être interrogé avec ou sans Ajax-Refresh.
Structure
- Déclaration d'un JavaBean.
- Le paramètre
idest surchargé car déjà utilisé par la mécanique du portail JCMS ; - Le paramètre
redirectest dépendant du context (Ajax) ;
- Le paramètre
- Validation par la methode
validate()qui peut positionner une redirection. - Affichage des erreurs via la JSP
doMessageBox.jsp. - Formulaire de saisie
- Le paramètre
opIddoit être fournit dans le cas d'une mise à jour ; - la classe CSS
confirmpermet l'affichage d'une demande de confirmation avant la soumission du formulaire.
- Le paramètre
Limitations
- La Portlet ne doit pas être mises en cache pour fonctionner correctement. Pour un usage exclusivement en Ajax il est possible de mettre un cache invalidé par une action de Portlet.
<%@ include file='/jcore/doInitPage.jsp' %>
<%@ include file='/jcore/portal/doPortletParams.jsp' %>
<%
PortletQueryForeach box = (PortletQueryForeach) portlet;
String pubId = request.getParameter("opId");
String redirect = jcmsContext.isAjaxRequest() ? PortalManager.getAjaxPortalUrl(box)
: ServletUtil.getResourcePath(request);
%>
<jsp:useBean id='formHandler' scope='page' class='generated.EditSmallNewsHandler'>
<jsp:setProperty name='formHandler' property='request' value='<%= request %>'/>
<jsp:setProperty name='formHandler' property='response' value='<%= response %>'/>
<jsp:setProperty name='formHandler' property='*' />
<jsp:setProperty name="formHandler" property="id" value="<%= pubId %>"/>
<jsp:setProperty name="formHandler" property="redirect" value="<%= redirect %>"/>
</jsp:useBean>
<%
if (isLogged && loggedMember.canPublish(SmallNews.class) && formHandler.validate()){
return;
}
%>
<%@ include file='/jcore/doMessageBox.jsp' %>
<%@ include file='doQuery.jsp' %>
<%@ include file='doSort.jsp' %>
<ul>
<%@ include file='doForeachHeader.jsp' %>
<li><jalios:link data='<%= itPub %>'/></li>
<%@ include file='doForeachFooter.jsp' %>
</ul>
<% if (isLogged && loggedMember.canPublish(SmallNews.class)) { %>
<form action="<%= ServletUtil.getResourcePath(request) %>" method="POST" name='editForm'>
<input type="text" name="title" value="" />
<input type="hidden" name="content" value="-" />
<input class="ajax-refresh confirm" type="submit" name="opCreate" value="Ajouter" title="Confirmer la création ?"/>
<input type="hidden" name="opId" value="" />
</form>
<% } %>
<%@ include file='doPager.jsp' %>
- Il n'est pas nécessaire de positionner la classe ajax-action sur les formulaires utilisant la méthode POST.
- La methode PortalManager.getAjaxUrl() est à utiliser dans les cas simple, elle ne tiens pas compte de l'usage. Elle est mise à jour en JCMS 6.0.2
4.3 Utilisations avancées
Cette section aborde des problématiques avancées qui apparaissent lors du développement de gabarits utilisant beaucoup de JavaScript.
Lors d'une requête Ajax-Refresh, les objets DOM du navigateur sont remplacés par de nouveaux objets créés à partir du flux retourné par l'appel Ajax. Par conséquent, le code JavaScript ne doit ni référencer ces objets ni mettre à jour ces références.
4.3.1 Rafraichissement
L'API Ajax-Refresh permet à une portlet de déclencher le rafraichissement d'une autre portlet. Ce rafraichissement peut s'accompagner d'un passage de paramètres.
Dans l'exemple ci-dessous, toutes les zones de la portlet ayant l'identifiant c_1234 seront rafraichies.
function refresh() {
JCMS.ajax.Refresh.refreshPortlet("c_1234","order=title");
}4.3.2 Observe Class
L'appel de fonction lors d'un clic sur un lien ou un bouton est une des taches récurrentes effectuées en JavaScript pour interagir avec le navigateur (présenter une boite modale, afficher un menu contextuel, proposer de l'édition inline, ...)
Une première solution consiste à utiliser l'attribut onclick:
<%@ include file="/jcore/doInitPage.jsp" %>
<% jcmsContext.addJavaScript("editinline.js"); %>
<div id="action-box">
<ul>
<li><a href="#" onclick="return callback(this);" class='edit-inline'>Editer A</a></li>
<li><a href="#" onclick="return callback(this);" class='edit-inline'>Editer B</a></li>
<li><a href="#" onclick="return callback(this);" class='edit-inline'>Editer C</a></li>
<li><a href="#" onclick="return callback(this);" class='edit-inline'>Editer D</a></li>
<li><a href="#" onclick="return callback(this);" class='edit-inline'>Editer E</a></li>
</ul>
</div>
Lorsqu'il y a de nombreux liens, une solution plus élégante consiste à brancher en JavaScript le callback.
Note
Il faut attendre la fin de l'exécution du JavaScript avant de cliquer.
'JCMS.plugin.EditInline'.namespace({
init: function() {
$$('DIV#action-box A.edit-inline').each(function(elm, idx){
Event.observe(elm, 'click', JCMS.plugin.EditInline.callback.bindAsEventListener(elm));
});
},
callback: function(event) {
Event.stop(event);
alert('Hello World');
}
});
Event.observe(window, 'load' , JCMS.plugin.EditInline.init );Mais lors d'un raffraichissement par Ajax-Refresh, les éléments DOM sont perdus et l'initialisation doit être relancée. La fonction JavaScript Util.observeClass() permet de résoudre ce problème:
'JCMS.plugin.EditInline'.namespace({
init: function() {
Util.observeClass('edit-inline', JCMS.plugin.EditInline.callback);
},
callback: function(event, elm, classname) {
Event.stop(event);
alert('Hello World');
}
});
Event.observe(window, 'load' , JCMS.plugin.EditInline.init);Le déclenchement du callback est effectué de façon lazy. Lors d'un clic la fonction recherche dans les éléments cliqués un élément correspondant à la classe CSS spécifiée. Il n'y a pas de pointeur sur les éléments du DOM ce qui simplifie l'Ajax-Refresh.
4.3.3 Auto-completion
Les portlets qui utilisent des frameworks complexes et qui s'initialisent au chargement de la page (Drag'n Drop, Autocomplete,...) doivent refaire manuellement l'initialisation.
Lors d'un Ajax-Refresh, des évènements JavaScript refresh:before et refresh:after sont envoyés. La DIV rafraichie est accessible avec la syntaxe : $(event.memo.wrapper)
Fichier HTM
<%@ include file="/jcore/doInitPage.jsp" %>
<% jcmsContext.addJavaScript("autocomplete.js"); %>
<div id="action-box">
<label>Nom du membre:</label>
<input id="box-input" type="text" name="nom" value=""/>
</div>Fichier JavaScript
- Au chargement la fonction
init()est appelée- la fonction
_initDiv()crée une DIV d'autocompletion. - la fonction
_initAutocomplete()construit l'Autocomplete.
- la fonction
- Lors d'un Refresh:Before _initAutocomplete() détruit l'Autocomplete
- Lors d'un Refresh:After _initAutocomplete() reconstruit l'Autocomplete
'JCMS.plugin.Autocomplete'.namespace({
init: function(event) {
// Create autocomplete DIV once
JCMS.plugin.Autocomplete._initDiv();
// Update autocomplete widget
JCMS.plugin.Autocomplete._initAutocomplete();
},
refresh : function(event){
var wrapper = $(event.memo.wrapper);
var input = $('box-input');
// input has ancestor wrapper
if (!input.ancestors().any(function(elm){ return elm == wrapper; })){
return;
}
// Update autocomplete widget
JCMS.plugin.Autocomplete._initAutocomplete();
},
_initDiv : function(){
if (JCMS.plugin.Autocomplete._acDiv){ return;}
var div = $(document.createElement('DIV'));
div.addClassName('autocomplete');
document.body.appendChild(div);
JCMS.plugin.Autocomplete._acDiv = div;
},
_initAutocomplete : function(){
// Delete
if (JCMS.plugin.Autocomplete._ac){
JCMS.plugin.Autocomplete._ac = null;
return;
}
// Create
var input = $('box-input');
var div = JCMS.plugin.Autocomplete._acDiv;
JCMS.plugin.Autocomplete._ac = new Ajax.Autocompleter(input, div, "jcore/autocomplete/acmember.jsp", {paramName: 'autocomplete', minChars: 2 });
}
});
Event.observe(window, 'load' , JCMS.plugin.Autocomplete.init);
Event.observe(document, 'refresh:after', JCMS.plugin.Autocomplete.refresh, false);
Event.observe(document, 'refresh:before', JCMS.plugin.Autocomplete.refresh, false);
Français

