10 nov. 2021 14:58:45 Thomas Dumont avatar

Intégration de Asynchronous Upload dans un plugin

Le plugin Asynchronous Upload est un plugin permettant de simplifier et de centraliser le framework d'upload asynchrone utilisé dans une application Lutèce. Il utilise actuellement le framework JavaScript JQuery File Upload pour gérer les uploads asynchrones de fichiers.

Description du fonctionnement

Le plugin asynchronous upload fournit des fichiers JavaScript et des macros Freemarker permettant de créer des inputs de type "fichier" afin de réaliser des uploads asynchrones. Le code HTML généré par ces macros utilise des fonctions JavaScript permettant de dialoguer en AJAX avec le serveur afin d'effectuer les uploads.

Les requêtes effectuées en AJAX sont traités par un gestionnaire d'upload côté serveur (appelé dans la suite handler). Ce handler a deux responsabilités :

  • Gérer le dialogue entre le JavaScript du client et le serveur.
  • Fournir les fichiers uploadés au service les nécessitant, et leur permettre d'en ajouter ou d'en supprimer.

Le dialogue avec le client est entièrement implémenté par le plugin. La gestion des fichiers uploadés (enregistrement, suppression, ...) est quand à elle du ressort de l'application utilisant ce plugin. Elle n'est donc pas implémentée par défaut.

Le fonctionnement standard d'un formulaire ayant des champs de fichiers d'upload asynchrone est le suivant :

  • Affichage du formulaire à l'utilisateur.
  • Saisie du formulaire par l'utilisateur, et upload éventuel de fichiers de façon asynchrone.
  • Soumission du formulaire par l'utilisateur.
  • Réception des données de formulaire soumis par le serveur.
  • Récupération de la liste des fichiers uploadés associés à chaque champ du formulaire soumis par le service traitant la requête via le handler.
  • Traitement métier des données (validation, sauvegarde, etc...).

Utilisation du plugin

Afin d'utiliser le plugin Asynchronous Upload, il faut effectuer les opérations suivantes :

  • Inclure le plugin Asynchronous Upload et l'activer.
  • Implémenter un gestionnaire d'upload côté serveur.
  • Inclure dans la page HTML un fichier JavaScript fourni par une JSP.
  • Ajouter dans le ou les formulaires des inputs de type fichier permettant de réaliser des uploads asynchrones à l'aide des macros Freemarker.

Inclusion du plugin

Le plugin peut être déclaré dans le POM d'un plugin ou d'un site Lutèce grâce au code suivant :

<dependency>
    <groupId>fr.paris.lutece.plugins</groupId>
    <artifactId>plugin-asynchronousupload</artifactId>
    <version>[1.0.1-SNAPSHOT,)</version>
    <type>lutece-plugin</type>
</dependency>

Implémentation du gestionnaire d'upload

Le gestionnaire d'upload doit implémenter l'interface fr.paris.lutece.plugins.asynchronousupload.service.IAsyncUploadHandler. Il est très fortement recommandé d'utiliser la classe abstraite fr.paris.lutece.plugins.asynchronousupload.service.AbstractAsynchronousUploadHandler. Dans la suite, nous supposerons que nous étendons cette classe.

Un handler doit donc au minimum implémenter les méthodes ci dessous :

public class UploadHandlerDemo extends AbstractAsynchronousUploadHandler
{
    private static final String HANDLER_NAME = "myHandler";

    /**
     * Vérifie si une liste de fichiers uploadés pour un champ donné sont valides
     * @param request La requête effectuée
     * @param strFieldName Le nom du champ ayant servi à uploader un fichier.
     * @param listFileItemsToUpload Liste des fichiers uploadés à vérifier.
     * @param locale La locale à utiliser pour afficher les messages d'erreur éventuels
     * @return Le message d'erreur, ou null si aucune erreur n'a été détéctée et si les fichiers sont valides.
     */
    @Override
    public String canUploadFiles( HttpServletRequest request, String strFieldName,
            List<FileItem> listFileItemsToUpload, Locale locale )
    {
        // TODO Auto-generated method stub
        return null;
    }

    /**
     * Permet de déclarer un fichier comme uploadé. L'implémentation de cette méthode est désormais en charge de la gestion du fichier.
     * @param fileItem Le fichier uploadé
     * @param strFieldName Le nom du champ auquel le fichier est associé
     * @param request La requête
     */
    @Override
    public void addFileItemToUploadedFilesList( FileItem fileItem, String strFieldName, HttpServletRequest request )
    {
        // TODO Auto-generated method stub
    }

    /**
     * Permet de récupérer la liste des fichiers uploadés pour un champ donné. La liste doit être ordonnée chronologiquement par date d'upload.
     * A chaque fichier un index sera associé correspondant à l'index du fichier dans la liste (le fichier le plus vieux aura l'index 0).
     * Deux appels successifs de cette méthode doivent donc renvoyer une liste ordonnée de la même manière.
     * @param strFieldName Le nom du champ dont on souhaite récupérer les fichiers
     * @param session la session de l'utilisateur utilisant le fichier. A n'utiliser que si les fichiers sont enregistrés en session.
     * @return La liste des fichiers uploadés pour le champ donné
     */
    @Override
    public List<FileItem> getListUploadedFiles( String strFieldName, HttpSession session )
    {
        // TODO Auto-generated method stub
        return null;
    }

    /**
     * Permet de supprimer un fichier précédemment uploadé
     * @param strFieldName Le nom du champ
     * @param session la session de l'utilisateur utilisant le fichier. A n'utiliser que si les fichiers sont enregistrés en session.
     * @param nIndex L'index du fichier dans la liste des fichiers uploadés.
     */
    @Override
    public void removeFileItem( String strFieldName, HttpSession session, int nIndex )
    {
        // TODO Auto-generated method stub
    }

    /**
     * Permet de définir le nom du handler. Ce nom doit être unique, et ne contenir que des caractères numériques (pas de points, de virgule, ...).
     * Il est recommandé de préfixer le nom du plugin, puis de suffixer un nom fonctionnel.
     * Attention, le nom du handler est différent du nom du bean Spring associé !
     */
    @Override
    public String getHandlerName( )
    {
        return HANDLER_NAME;
    }
}

Le handler doit ensuite être déclaré comme un bean Spring dans un fichier de contexte.

Exemple Vous pourrez trouver un exemple d'implémentation de gestionnaire d'upload dans le plugin genericattributes ici .

Inclusion du fichier JavaScript

Une fois le handler créé, il faut inclure dans la page HTML contenant le formulaire le script JavaScript fournit par la JSP jsp/site/plugins/asynchronousupload/GetMainUploadJs.jsp. Cette JSP utilise les paramètres suivants :

  • handler : Nom du handler (tel que défini dans le service). Ce paramètre est obligatoire.
  • maxFileSize : Taille maximale (en octets) de chaque fichier uploadé. Ce paramètre est facultatif, la valeur par défaut étant 2097152.

Exemple d'inclusion :

<script type="text/javascript" src="jsp/site/plugins/asynchronousupload/GetMainUploadJs.jsp?handler=myAsynchronousUploadHandler" ></script>

Info La JSP doit être appelée pour chaque handler utilisé sur la page.

Macros Freemarker

Il ne reste donc plus qu'à créer les input qui vont permettre de réaliser des uploads asynchrones.

Ces inputs peuvent être créés grâces aux macros contenues dans le template skin/plugins/asynchronousupload/upload_commons.html. Les macros de ce fichier sont accessibles en l'important dans vos templates. Pour cela, il faut ajouter la ligne suivante, par exemple en en-tête de vos templates :

<#include "/skin/plugins/asynchronousupload/upload_commons.html" />

Une fois ce fichier importé, deux macros peuvent être utilisées :

/**
 * Macro permettant d'inclure les fichiers JavaScript et CSS nécessaires au fonctionnement des inputs d'upload asynchrones.
 * Cette macro doit être utilisées si le service d'inclusion du portail Lutèce ne fonctionne pas (exemple : dans le Back Office, dans des JSP spécifiques, ...)
 */
<#macro addRequiredJsFiles>

/**
 * Macro permettant d'ajouter un input de type fichier initialisé pour l'upload asynchrone.
 * Cette macro créera également un bloc permettant d'afficher la liste des fichiers upload. Cette liste sera mise à jour lors des uploads.
 * @param fieldName Nom de l'input à générer.
 * @param handler Instance du service gestionnaire d'upload associé.
 * @param listUploadedFiles Liste des fichiers ayant déjà été uploadés. Chaque objet de cette liste doit avoir un attribut 'title' ou 'name'.
 * @param inputCssClass Classe CSS à ajouter à l'input généré. La valeur par défaut est une chaîne de caractères vide.
 * @param multiple True pour autoriser la sélection simultanée de plusieurs fichiers, false pour l'interdire. Nécessite un navigateur compatible HTML5 pour fonctionner.
 */
<#macro addFileInputAndfilesBox fieldName handler listUploadedFiles inputCssClass='' multiple=false>

La macro <#macro addFileInputAndfilesBox fieldName handler listUploadedFiles inputCssClass='' multiple=false> fait appel aux macros ci-dessous :

/**
 * Macro permettant d'ajouter un input de type fichier initialisé pour l'upload asynchrone.
 * @param fieldName Nom de l'input à générer.
 * @param handler Instance du service gestionnaire d'upload associé.
 * @param inputCssClass Classe CSS à ajouter à l'input généré. La valeur par défaut est une chaîne de caractères vide.
 * @param multiple True pour autoriser la sélection simultanée de plusieurs fichiers, false pour l'interdire. Nécessite un navigateur compatible HTML5 pour fonctionner.
 */
<#macro addFileInput fieldName handler inputCssClass multiple=false>

/**
 * Macro créant un bloc permettant d'afficher la liste des fichiers upload. Cette liste sera mise à jour lors des uploads.
 * @param fieldName Nom de l'input à générer.
 * @param handler Instance du service gestionnaire d'upload associé.
 * @param listUploadedFiles Liste des fichiers ayant déjà été uploadés. Chaque objet de cette liste doit avoir un attribut 'title' ou 'name'.
 */
<#macro addUploadedFilesBox fieldName handler listUploadedFiles >

Vous pouvez utiliser la macro addFileInputAndfilesBox pour créer autant d'inputs que nécessaire.

Le code HTML généré aura donc un input permettant l'upload asynchrone et utilisant le service de handler spécifié en paramètre.

Rechargement de fichiers précédemment uploadés

Si des fichiers ayant déjà été uploadés doivent être ajoutés à la liste des fichiers uploadés par un utilisateur (exemple : rechargement d'un formulaire, ...), il faut les déclarer auprès du gestionnaire d'upload. Pour cela, il suffit d’appeler la méthode IAsynchUploadHandler.addFileItemToUploadedFilesList( FileItem, String, HttpServletRequest) du gestionnaire d'upload associé.

Chacun de ces fichiers sera alors considéré comme ayant été uploadé par l'utilisateur. Les fichiers seront donc inclus dans la liste des fichiers uploadés, et l'utilisateur aura donc la possibilité de les supprimer comme n'importe quel autre fichier.

Uploads synchrones

Une fois l'upload asynchrone mis en place, il ne reste plus qu'à mettre en place la possibilité pour les utilisateurs n'ayant pas de JavaScript d'utiliser l'upload synchrone. Cela peut être mis en place par des traitements spécifiques côté serveur.

Ajout de fichier

La méthode IAsynchUploadHandler.hasAddFileFlag( HttpServletRequest, String ) du gestionnaire d'upload permet de vérifier si un drapeau indiquant qu'un fichier a été uploadé pour un input spécifique est présent dans la requête. Si cela est le cas, la méthode IAsynchUploadHandler.addFilesUploadedSynchronously( HttpServletRequest, String) permet d'ajouter les fichiers correspondants au gestionnaire d'upload. Ceux-ci seront alors traités comme n'importe quel autre fichier uploadé.

Pour que l'upload synchrone fonctionne correctement, il faut donc effectuer les opérations suivantes :

  • Afficher le formulaire avec la liste des fichiers déjà uploadés initialisée
  • Lorsque le formulaire est soumis, vérifier si le drapeau d'upload est présent pour chaque input fichier.
  • Pour chaque drapeau trouvé, ajouter les fichiers associés au gestionnaire d'upload.
  • Si au moins 1 drapeau est présent, afficher à nouveau le formulaire avec la liste des fichiers mis à jour. Si aucun drapeau n'est présent, traiter la requête comme une validation du formulaire.

Suppression de fichiers

La suppression synchrone des fichiers fonctionne de manière analogue à l'upload : la méthode IAsynchUploadHandler.hasRemoveFlag( HttpServletRequest, String ) permet de tester la présence d'un drapeau de suppression de fichiers pour un input donné, et la méthode IAsynchUploadHandler.doRemoveFile( HttpServletRequest, String ) permet d'effectuer la suppression des fichiers indiqués.