Exemples
Exemples
Description
Cette partie contient plusieurs exemples complets et concrets de développement d’un plugin.
Le développement de trois fonctions :
- la première permettant de transformer le contenu d’un champ date en une date en toutes lettres
- la seconde permettant, de calculer et de mettre à jour, une superficie et un prix en fonction de la longueur, de la largeur et du prix au m² d’un terrain
- La troisième permettant de présenter le fonctionnement de la paramservlet et de l’implémentation de l’interface de paramétrage spécifique
Et le développement de 4 composants :
- le premier affichant la météo d’une ville renseignée
- le second affichant un compteur qui s’incrémente au clic sur un bouton et se décrémente au clic sur un autre bouton
- le troisièmre utilisant la servletFile qui transforme deux images : une en noir et blanc et l’autre qui effectue une rotation
- le quatrième utilisant la YbList pour fabriquer une liste utilisant le style de YellowBox
Fonction 1
Développement
La fonction prend un seul paramètre en entrée qui correspond au champ date à transformer et retourne un String
qui est la valeur en toutes lettres.
La définition des paramètres dans la classe Fonction se fait comme suit :
public Params getParams() {
Params parameters = new Params();
Param valueDate = parameters.addNew(Type.DATE, InOut.IN, "Valeur du champ date", PARAMVALDATESOURCE);
valueDate.setDescription("Sélection du champ date à convertir");
valueDate.setMandatory(true);
Param lettersDate = parameters.addNew(Type.STRING, InOut.OUT, "Champ date en lettres", PARAMVALDATEDEST);
lettersDate.setDescription("Sélection du champ où sauver la valeur de la date en lettres");
lettersDate.setMandatory(true);
return parameters;
}
Ici, nous avons défini deux constantes static final qui correspondent à l’attribut varName
de l’objet Param
.
public static final String PARAMVALDATESOURCE = "paramValDateSource";
public static final String PARAMVALDATEDEST = "paramValDateDest";
Cela permet d’utiliser des constantes pour récupérer les valeurs des paramètres.
Ensuite le traitement de la fonction est assez simple :
public ParamValues call() {
// valeur de la date du paramètre
Date dateSource = getParamValues().getDateValue(PARAMVALDATESOURCE);
// Formatage de la date
SimpleDateFormat formatter = new SimpleDateFormat("dd MMMM yyyy hh:mm:ss");
// Création du paramètre de sortie
ParamValues out = new ParamValues();
out.addStringValue(PARAMVALDATEDEST, formatter.format(dateSource));
return out;
}
On récupère la valeur du paramètre du champ Date, puis on la formate à l’aide d’un SimpleDateFormat
avec le pattern voulu.
Enfin, on créé un objet ParamValues
qui correspond au paramètre de sortie de la fonction. On lui ajoute le paramètre de sortie PARAMVALDATEDEST ainsi que sa valeur formatée.
Paramétrage
Après avoir chargé le package dans YellowBox, il faut se rendre sur la liste des Plugins et cliquer sur “Ajouter” dans le bandeau haut.
Saisir un nom, sélectionner le plugin chargé, choisir la fonction dateToLetters dans “Instance de” et sélectionner une Table, ici on choisira la table Sociétés.
Le 1er paramètre correspond au paramètre d’entrée, il faut renseigner le champ date à transformer, ici Date.
Le second paramètre est le paramètre de sortie, il doit correspondre à un champ alphanumérique où le résultat sera injecté, ici Date en lettres.
Valider la création du plugin.
Nous souhaitons fournir en utilisation un accès à cette fonction depuis une fiche Sociétés. Pour cela, il est nécessaire de créer un processus qui exécutera ce plugin.
Pour ce faire, il faut se rendre sur la liste des Processus en paramétrage en déroulant le sous menu “Automatismes”, puis cliquer sur “Ajouter”.
Saisir un nom de processus, choisir “Fiche” dans le type et sélectionner la table, ici Sociétés.
Cliquer sur “Suivant” puis cliquer sur le logo “plus” à droite de “Fonctions associées au processus”, sélectionner Restriction “Fiche”, puis “Plugin” dans le type et enfin choisir le plugin paramétré.
Valider la fonction processus et le processus et passer en mode utilisation.
Utilisation
Ouvrir une fiche Sociétés, ouvrir le mode éditeur en bas de l’écran et sélectionner un objet dans l’écran pour ajouter le processus. Cliquer sur le bouton “Ajouter”, choisir “Processus” dans Type, sélectionner le processus précédemment paramétré, ici dateToLetters et valider.
Un bouton processus apparaît alors et il suffit de cliquer dessus pour lancer son exécution :
Au clic sur le bouton processus, la fiche est sauvegardée puis le plugin est exécuté.
On peut observer le résultat dans le champ Date en lettres.
Fonction 2
Développement
La deuxième fonction prend plusieurs paramètres en entrée qui correspondent aux différents champs de YellowBox nécessaires aux calculs :
public Params getParams() {
Params parameters = new Params();
Param idElement = parameters.addNew(Type.STRING, InOut.IN, "idElement", PARAMVALIDELEMENT);
idElement.setDescription("Id element sur lequel la fonction va s'appliquer");
idElement.setMandatory(true);
Param longueur = parameters.addNew(Type.IDFIELD, InOut.IN, "Champ longueur", PARAMIDFIELDLONGUEUR);
longueur.setDescription("Référence du champ longueur d'un terrain");
longueur.setMandatory(true);
Param largeur = parameters.addNew(Type.IDFIELD, InOut.IN, "Champ largeur", PARAMIDFIELDLARGEUR);
largeur.setDescription("Référence du champ largeur d'un terrain");
largeur.setMandatory(true);
Param prixMetreCarre = parameters.addNew(Type.IDFIELD, InOut.IN, "Champ prix m2", PARAMIDFIELDPRIXMETRECARRE);
prixMetreCarre.setDescription("Référence du champ du prix au m2 d'un terrain");
prixMetreCarre.setMandatory(true);
Param prix = parameters.addNew(Type.IDFIELD, InOut.IN, "Champ prix du terrain", PARAMIDFIELDPRIX);
prix.setDescription("Référence du champ du prix d'un terrain");
prix.setMandatory(true);
Param superficie = parameters.addNew(Type.IDFIELD, InOut.IN, "Champ superficie", PARAMIDFIELDSUPERFICIE);
superficie.setDescription("Référence du champ superficie d'un terrain");
superficie.setMandatory(true);
return parameters;
}
Nous passons des paramètres de type IDFIELD pour voir comment récupérer les valeurs de ces champs à partir de leur ID.
La fonction call() est expliquée en détail ci-dessous.
La première partie de la méthode permet de récupérer l’ensemble des paramètres définis :
// Récupération des paramètres
String idElement = getParamValues().getStringValue(PARAMVALIDELEMENT);
Record terrain = getYellowboxEntryPoint().getServices().getRecordService().getRecordFromId(idElement);
String idFieldLongueur = getParamValues().getStringValue(PARAMIDFIELDLONGUEUR);
String idFieldLargeur = getParamValues().getStringValue(PARAMIDFIELDLARGEUR);
String idFieldPrixMettreCarre = getParamValues().getStringValue(PARAMIDFIELDPRIXMETRECARRE);
String idFieldPrix = getParamValues().getStringValue(PARAMIDFIELDPRIX);
String idFieldSuperficie = getParamValues().getStringValue(PARAMIDFIELDSUPERFICIE);
On récupère un objet Record à partir de l’id de l’elément sur lequel on exécute le processus (id de la fiche Terrains ici) en utilisant le service Record getRecordFromId.
On va ensuite créé les objet Field que nous allons mettre à jour dans la fiche Terrains : le champ Superficie et le champ Prix :
// Création des Champs qui vont être mis à jour
Field fieldSuperficie = new Field();
fieldSuperficie.setId(idFieldSuperficie);
Field fieldPrix = new Field();
fieldPrix.setId(idFieldPrix);
On boucle ensuite sur l’ensemble des champs du Record pour récupérer les valeurs des champs longueur, largeur et prix en fonction de leur ID :
int longueur = 0;
int largeur = 0;
int prixMettreCarre = 0;
// On boucle sur l'ensemble des champs du record
// pour récupérer les valeurs en fonction des ID de champ
for(Value value : terrain.getValues()) {
String fieldId = value.getField().getId();
if(fieldId.equals(idFieldLongueur)) {
longueur = Integer.valueOf(value.getValue());
} else if(fieldId.equals(idFieldLargeur)) {
largeur = Integer.valueOf(value.getValue());
} else if(fieldId.equals(idFieldPrixMettreCarre)) {
prixMettreCarre = Integer.valueOf(value.getValue());
}
}
Une fois les valeurs des champs trouvées, on réalise les calculs de la superficie et du prix du terrain :
// calcul de la superficie et du prix du terrain
int superficie = longueur * largeur;
int prix = prixMettreCarre * superficie;
On créé ensuite les Value pour la superficie et le prix, on y associe les bons champs et les valeurs calculées et on les ajoute dans une liste de Value :
// Création des Value Superficie et Prix
Value valSuperficie = new Value();
valSuperficie.setField(fieldSuperficie);
valSuperficie.setValue(String.valueOf(superficie));
values.add(valSuperficie);
Value valPrix = new Value();
valPrix.setField(fieldPrix);
valPrix.setValue(String.valueOf(prix));
values.add(valPrix);
Il ne reste plus qu’à mettre la liste de Value dans le Record terrain, à crééer un objet ImportRecord à partir du record et à appeler le service createOrUpdateV2 pour mettre à jour le record :
// on set la liste de values au Record et on execute le service de mise à jour
terrain.setValues(values);
// Création de l'objet ImportRecord
ImportRecord irTerrain = new ImportRecord(terrain);
getYellowboxEntryPoint().getServices().getRecordService().createOrUpdateV2(irTerrain);
On retourne un objet ParamValues vide dans la méthode call() car il n’y a pas de paramètre de sortie.
// pas de paramètres de sortie, on retourne un objet vide
return new ParamValues();
Paramétrage
Nous avons préalablement créé une table Terrains avec 5 champs :
- un champ entier longueur
- un champ entier largeur
- un champ entier superficie
- un champ entier prix au m²
- un champ entier prix total du terrain
Le paramétrage est similaire à la première fonction, il faut sélectionner les bons champs correspondants à nos différents paramètres.
Utilisation
Une fois le plugin paramétré et le processus contenant le plugin créé, il est possible d’exécuter ce processus en liste. Pour ce faire, il faut se rendre dans le menu de paramétrage puis dérouler le sous menu “Utilisateurs et accès” et cliquer sur “Accès”. Dans l’arbre de structure, déplier l’objet “Traitements” tout en bas puis “Processus” et cliquer sur le processus nouvellement créé. Changer la visibilité en liste pour “Toujours” puis valider.
Retourner sur la liste de la table Terrains, sélectionner des fiches pour exécuter le processus, cliquer sur le bouton avec le logo “éclair” puis cliquer sur le nom du processus et valider l’exécution. Cela va exécuter le processus sur l’ensemble des fiches selectionnés.
Le résultat de l’exécution de la fonction sur une fiche est le suivant :
Fonction 3
Développement
La fonction propose 4 paramètres d’entrée : la saisie d’un login, d’un mot de passe, un paramètre déclencheur
non visible et un paramètre Autre information
visible selon la valeur du paramètre declencheur.
La définition des paramètres dans la classe Fonction se fait comme suit :
public Params getParams() {
Params parameters = new Params();
Param param = new Param();
param.setType(Type.STRING);
param.setInOut(InOut.IN);
param.setName("Login");
param.setVarName("login");
param.setDescription("");
param.setMandatory(true);
parameters.add(param);
param = new Param();
param.setType(Type.STRING);
param.setInOut(InOut.IN);
param.setName("Password");
param.setVarName("password");
param.setDescription("");
param.setMandatory(true);
parameters.add(param);
param = new Param();
param.setType(Type.STRING);
param.setInOut(InOut.IN);
param.setName("autre info");
param.setVarName("other");
param.setDescription("");
param.setMandatory(true);
parameters.add(param);
param = new Param();
param.setType(Type.STRING);
param.setInOut(InOut.IN);
param.setName("declencheur");
param.setVarName("declencheur");
param.setDescription("");
param.setMandatory(true);
parameters.add(param);
return parameters;
}
Nous ne nous intéressons pas ici à l’utilisation du plugin car cet exemple sert à comprendre comment implémenter sa propre interface de paramétrage, et les interactions possibles.
Nous implémentons donc la méthode getParamInstanceUI()
comme suit :
public UIRoot getParamInstanceUI() {
UIRoot root = new UIRoot();
UIPanel panel = new UIPanel("Connexion");
panel.add(new UIParamDefault("login"));
panel.add(new UIParamDefault("password"));
UIRow line = new UIRow();
UIHtml uiHtml = new UIHtml();
uiHtml.setHtml("<a id=\"executeParamServlet\" href=\"javascript:callParamServlet('testparamservlet')\">Tester la connexion</a>");
line.add(uiHtml);
panel.add(line);
UIParamDefault u = new UIParamDefault("other");
String declencheurValue =null;
try {
declencheurValue = this.getParamValues().getStringValue("declencheur");
}catch(Exception e) {
declencheurValue = null;
}
if(declencheurValue != null && this.getParamValues().getStringValue("declencheur").equals("2") ) {
uiHtml = new UIHtml();
uiHtml.setHtml("Fonctionnement ok");
panel.add(uiHtml);
panel.add(u);
}
root.add(panel);
return root;
}
On crée un uiElement de type UIRoot root, racine de l’interface. Nous insérons dedans un élément UIPanel pour afficher un cadre. Nous utilisons la notion de UILine pour définir une ligne dans laquelle une première colonne contiendra une zone HTML UIHtml. Enfin, nous définissons, sous condition d’une valeur spécifique dans le paramètre declencheur
, l’affichage d’une zone UIHtml et l’affichage du paramètre Autre information
.
Pour finaliser notre affichage, nous implémentons une ParamServlet nommée TestParamServlet
et recencée dans la méthode getParamServlets
:
public List<Class<? extends ParamServlet>> getParamServlets() {
List<Class<? extends ParamServlet>> listParamServlet = new ArrayList<>();
listParamServlet.add(TestParamServlet.class);
return listParamServlet;
}
Cette ParamServlet est implémentée comme suit :
public class TestParamServlet extends ParamServlet {
@Override
public ParamValues call(ParamValues paramValues) {
System.out.println("login : "+paramValues.getStringValue("login"));
for(ParamValue pvTemp :paramValues.getValues()) {
if(pvTemp.getVarName().equals("declencheur")) {
pvTemp.setString("2");
break;
}
}
return paramValues;
}
@Override
public String getName() {
return "testparamservlet";
}
}
Elle présente une méthode getName()
définissant le nom d’appel à réaliser à travers la méthode javascript callParamServlet (en exemple dans l’implémentation de la méthode getParamInstanceUI
), une méthode call
qui représente la traitement souhaité. Cette méthode call
prend met à disposition un ParamValues reprenant tous les paramètres issus de l’interface avec leurs valeurs. Elle retourne aussi un ParamValues fournissant l’ensemble des paramvalues avec eventuiellement leurs valeurs modifiées (comme dans l’exemple ci-dessus où la valeur du paramètre declencheur
a pris la valeur “2”).
Si la valeur retournée est Null, les valeurs fournies en entrée seront conservées.
Après chaque exécution d’un ParamServlet, la méthode getParamInstanceUI
est réexécutée.
Paramétrage
Après avoir chargé le package dans YellowBox, il faut se rendre sur la liste des Plugins et cliquer sur “Ajouter” dans le bandeau haut.
Saisir un nom, sélectionner le plugin chargé, choisir la fonction Fonction dynamicParam dans “Instance de”.
Vous pouvez saisir des informations sur les deux paramètres visibles initialement (login et password).
Un lien est disponible pour exécuter votre ParamServlet. En cliquant dessus, l’exécution du TestParamServlet a lieu et l’interface de paramétrage se recalcul présentant alors un paramètre complémentaire Autre information.
Composant 1
Développement
Le composant prend un seul paramètre en entrée qui correspond à la ville où l’on souhaite obtenir la météo.
La définition des paramètres dans la classe Composant se fait comme suit :
public Params getParams() {
Params parameters = new Params();
// Paramètre ville de type String
Param location = parameters.addNew(Type.STRING, InOut.IN, "Ville météo", PARAMLOCATION);
location.setDescription("Ville pour obtenir la météo");
location.setMandatory(true);
return parameters;
}
Il n’y a pas de paramètre de sortie dans un composant.
Pour ce composant, nous utilisons trois fichiers javascript :
- la librairie JQuery via un CDN
- la librairie openweather.js utilisant l’API OpenWeatherMap (Source)
- un fichier weather.js rassemblant notre code javascript
La méthode getListJs()
permettant de renseigner les fichiers javascript est implémentée comme suit :
public List<String> getListJs() {
List<String> listJS = new ArrayList<>();
listJS.add("https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.js");
listJS.add("/js/openweather.min.js");
listJS.add("/js/weather.js");
return listJS;
}
La méthode getListCss()
permet de renseigner les fichiers css qui vont styliser le composant :
public List<String> getListCss() {
List<String> listCss = new ArrayList<>();
listCss.add("/css/weather.css");
return listCss;
}
L’affichage du composant est décrit dans le fichier weather.html :
<body lang="en">
<div class="weather-wrapper hide">
<img src="" class="weather-icon" alt="Weather Icon" />
<p>
<strong>Place</strong> <br />
<span class="weather-place"></span>
</p>
<p>
<strong>Temperature</strong> <br />
<span class="weather-temperature"></span>
( <span class="weather-min-temperature"></span> -
<span class="weather-max-temperature"></span> )
</p>
<p>
<strong>Description</strong> <br />
<span class="weather-description capitalize"></span>
</p>
<p>
<strong>Humidity</strong> <br />
<span class="weather-humidity"></span>
</p>
<p>
<strong>Wind speed</strong> <br />
<span class="weather-wind-speed"></span>
</p>
<p>
<strong>Sunrise</strong> <br />
<span class="weather-sunrise"></span>
</p>
<p>
<strong>Sunset</strong> <br />
<span class="weather-sunset"></span>
</p>
</div>
</body>
<script>
// Initialisation du widget uniquement lors du chargement de toute la page y compris les scripts
$(window).on('load', function() {
load_weather('${location}');
});
</script>
Avec son style, décrit dans le fichier weather.css
body {
font: 16px Arial, Helvetica, sans-serif;
margin: 0;
}
.weather-wrapper {
background: skyblue;
margin: 5% 0 5% 5%;
padding: 40px 5%;
float: left;
color: white;
width: 70%;
max-width: 400px;
}
strong {
color: steelblue;
}
.capitalize {
text-transform: capitalize;
}
.hide {
display: none;
}
La variable ${location} correspond au paramètre d’entrée PARAMLOCATION et est passé au fichier html grâce à la méthode getHtml() :
public String getHtml() {
// chargement du fichier HTML
String html = getRessourceAsString("/html/weather.html");
// passage du paramètre location défini dans le fichier html
html = html.replace("${location}", getParamValues().getStringValue(PARAMLOCATION));
return html;
}
La méthode load_weather est défini dans le fichier weather.js et appelle la méthode d’initialisation du Widget OpenWeather avec les bons paramètres :
load_weather = function(location) {
$('.weather-temperature').openWeather({
key: 'c9d49310f8023ee2617a7634de23c2aa',
city: location,
descriptionTarget: '.weather-description',
windSpeedTarget: '.weather-wind-speed',
minTemperatureTarget: '.weather-min-temperature',
maxTemperatureTarget: '.weather-max-temperature',
humidityTarget: '.weather-humidity',
sunriseTarget: '.weather-sunrise',
sunsetTarget: '.weather-sunset',
placeTarget: '.weather-place',
iconTarget: '.weather-icon',
key: '895b596f10afb3a6edb1866f76edda15', // clé à renseigner https://openweathermap.org/
success: function(data) {
// show weather
$('.weather-wrapper').show();
},
error: function(data) {
console.log(data.error);
$('.weather-wrapper').remove();
}
});
}
Paramétrage
Le paramétrage du composant est assez simple : on indique un nom, on sélectionne le plugin et on choisit l’instance Composant. Pour pouvoir insérer le composant sur un Accueil de YellowBox, on ne sélectionne pas de table.
On renseigne alors l’unique paramètre qui est la ville de la météo.
Utilisation
Pour insérer le composant, nous allons nous rendre sur l’Accueil de YellowBox et créé un onglet “weather” dans le groupe d’onglets.
Ouvrir ensuite le mode éditeur et sélectionner ce nouvel onglet, puis cliquer sur “Ajouter”. Sélectionner “Composant” dans Type, puis le composant paramétré et valider.
Le composant s’afficher alors dans notre onglet. Il est possible de l’agrandir en largeur en le sélectionnant via le mode éditeur puis en cliquant sur “+” sur la ligne “Largeur”.
Le résultat est le suivant :
Composant 2
Ce second composant utilise une servlet pour comprendre son utilisation.
Développement
Ce composant n’a pas de paramètres.
Il comprend deux fichiers javascript (JQuery et un propre), un fichier de style css et un fichier HTML.
Il faut déclarer l’utilisation de Servlet comme suit :
public List<Class<? extends Servlet>> getServlets() {
List<Class<? extends Servlet>> listServlet = new ArrayList<>();
listServlet.add(CompteurServlet.class);
return listServlet;
}
Le fichier HTML est structuré comme suit :
<button id="buttonplus" onclick="majNumber('incremente');" class="btn btn-yellow">
+
</button>
<span id="text-compteur">
${compteur}
</span>
<button id="buttonmoins" onclick="majNumber('decremente');" class="btn btn-yellow">
-
</button>
Ainsi que le fichier CSS associé :
.btn {
display: inline-block;
font-weight: 400;
text-align: center;
white-space: nowrap;
vertical-align: middle;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
border: 1px solid transparent;
padding: .375rem .75rem;
font-size: 1rem;
line-height: 1.5;
border-radius: .25rem;
transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;
width: 100px;
font-weight: 600;
}
.btn:hover {
text-decoration: none;
}
.btn-yellow {
color: #212529;
background-color: #ffc107;
border-color: #ffc107;
}
Il comprend deux boutons : un + et un - permettant d’incrémenter ou de décrementer la valeur ${compteur}.
Cette valeur est passé au fichier HTML comme pour l’exemple du 1er composant dans la méthode getHtml() :
public String getHtml() {
// chargement du fichier HTML
String html = getRessourceAsString("/html/compteur.html");
// passage du paramètre compteur défini dans le fichier html
html = html.replace("${compteur}", String.valueOf(compteur));
return html;
}
Cette variable compteur correspond à un attribut de la classe CompteurComponent, initialisé à 0.
private int compteur = 0;
La fonction javascript majNumber prend en paramètre le type d’opération (“incrémente” ou “décrémente”) et appelle la servlet CompteurServlet avec le paramètre nommé “typeOperation” qui effectuera le traitement côté serveur.
majNumber = function(type){
callServlet('CompteurServlet',{'typeOperation' : type},function(data) {
$('#text-compteur').html(data);
});
};
La servlet reçoit donc ce paramètre “typeOperation” et effectue le traitement suivant :
public String call(ParamValues paramsServlet) {
// récupération du type d'opération
String typeOperation = paramsServlet.getStringValue("typeOperation");
CompteurComponent compteur = (CompteurComponent) getComponent();
if(typeOperation.equals("incremente")) {
return String.valueOf(compteur.getNext());
} else if (typeOperation.equals("decremente")) {
return String.valueOf(compteur.getPrevious());
}
// Si pas le bon type, on renvoie le compteur tel quel
return String.valueOf(compteur.getCompteur());
}
Le traitement est assez simple : si on reçoit la valeur “incrémente”, on appelle la fonction getNext du Component (à implémenter) qui incrémente et renvoit la valeur de l’attribut compteur du Component. Sinon, si on reçoit “décrémente”, on appelle la fonction getPrevious (à implémenter) qui décrémente et renvoit la valuer de l’attribut compteur. Si le type ne correspond à aucune de ces deux valeurs, on renvoie la valeur du compteur telle quelle avec la méthode getCompteur du Component (à implémenter).
Paramétrage
Le paramétrage de ce composant est très simple puisqu’il n’y a aucun paramètre.
Utilisation
Comme pour le précédent composant, on peut le positionner sur un Accueil de YellowBox et le résultat est le suivant :
Après 4 clics sur le bouton + :
Si on quitte l’affichage de l’Accueil, qu’on se rend ailleurs dans YellowBox et qu’on retourne sur l’affichage du composant, la valeur du compteur est conservée.
Composant 3
Ce troisième composant utilise une servletFile pour comprendre son utilisation.
Développement
Le code HTML suivant affiche deux sélecteurs de fichiers acceptant des images, lorsque l’utilisateur sélectionne une image, on l’affiche en dessous du sélecteur. Une fois les deux images selectionnés, l’utilisateur peut cliquer sur le bouton ‘Upload’ et les deux images sont envoyés à la Servlet.
Dans le code de la Servlet, nous modifions la première image en noir et blanc et la seconde nous effectuons une rotation à 90 degrés. Nous renvoyons le base64 de ces deux images au client pour les afficher.
<div id="plugin">
<div id="bw">
<input id="input-image-bw" type="file" name="image" accept="image/*" />
<div id="display-images-bw" style="display: none;">
<img width="200px;" height="200px;" id="img-original-bw" alt="original img bw" title="original image to bw"/>
<img width="200px;" height="200px;" id="img-bw" alt="bw img" title="bw image"/>
</div>
</div>
<div id="rotate">
<input id="input-image-rotate" type="file" name="image" accept="image/*" />
<div id="display-images-rotate" style="display: none;">
<img width="200px;" height="200px;" id="img-original-rotate" alt="original img rotate" title="original image to rotate"/>
<img width="200px;" height="200px;" id="img-rotate" alt="rotate img" title="rotate image"/>
</div>
</div>
<button id="btn-upload" onclick="uploadImage();">Upload</button>
</div>
document.addEventListener("DOMContentLoaded", function() {
//bw = black and white
var reader;
var extensionbw;
var imagebw;
var extensionrotate;
var imagerotate;
let inputBw = document.getElementById("input-image-bw");
let intputRotate = document.getElementById("input-image-rotate");
inputBw.onchange = function(event) {
imagebw = inputBw.files[0];
extensionbw = imagebw.type;
reader = new FileReader();
reader.readAsDataURL(imagebw);
reader.onloadend = () => {
var base64 = reader.result;
document.getElementById("img-original-bw").src = base64;
document.getElementById("display-images-bw").style.display = 'block';
}
}
intputRotate.onchange = function(event) {
imagerotate = intputRotate.files[0];
extensionrotate = imagerotate.type;
reader = new FileReader();
reader.readAsDataURL(imagerotate);
reader.onloadend = () => {
var base64 = reader.result;
document.getElementById("img-original-rotate").src = base64;
document.getElementById("display-images-rotate").style.display = 'block';
}
}
uploadImage = function() {
var formData = new FormData();
formData.append('image-bw',imagebw);
formData.append('type-bw',extensionbw);
formData.append('image-rotate', imagerotate);
formData.append('type-rotate',extensionrotate);
callServletFile('ServletFichier', formData, function(data) {
var json = JSON.parse(data);
var base64bw = "data:image/" + extensionbw+";base64," + json.imgbw;
document.getElementById("img-bw").src = base64bw;
var base64rotate = "data:image/" + extensionrotate+";base64," + json.imgrotate;
document.getElementById("img-rotate").src = base64rotate;
},
function(error) {
console.log("Error component",error);
});
}
});
Les deux images sont récupérés directement des input type file via un objet FileReader
.
La servlet que nous appelons est ServletFichier
.
public class ServletFichier extends Servlet {
@Override
public String call(ParamValues params) {
byte[] toBW = params.getFileValue("image-bw"); // récupération de la 1ere image
byte[] toRotate = params.getFileValue("image-rotate"); // récupération de la 2nde image
String extensionBw = params.getStringValue("type-bw"); // récupération du paramètre 'type-bw'
String extensionRotate = params.getStringValue("type-rotate"); // récupération du paramètre 'type-rotate'
extensionBw = extensionBw.substring(extensionBw.indexOf('/') +1, extensionBw.length());
extensionRotate = extensionRotate.substring(extensionRotate.indexOf('/') +1, extensionRotate.length());
ByteArrayInputStream baisBw = new ByteArrayInputStream(toBW);
ByteArrayInputStream baisRotate = new ByteArrayInputStream(toRotate);
BufferedImage imageBw = null;
BufferedImage imageRotate = null;
try {
imageBw = ImageIO.read(baisBw);
imageRotate = ImageIO.read(baisRotate);
} catch(Exception e) {
// handle exception
}
BufferedImage bw = bwImage(imageBw);
BufferedImage rotated = rotateImage(imageRotate);
ByteArrayOutputStream outBw = new ByteArrayOutputStream();
ByteArrayOutputStream outRotate = new ByteArrayOutputStream();
try {
ImageIO.write(bw, extensionBw, outBw);
ImageIO.write(rotated, extensionRotate, outRotate);
} catch (IOException e) {
// handle exception
}
JSONObject json = new JSONObject(); // création de l'objet json de retour
json.put("imgbw", Base64.getEncoder().encodeToString(outBw.toByteArray()));
json.put("imgrotate", Base64.getEncoder().encodeToString(outRotate.toByteArray()));
return json.toString();
}
private BufferedImage bwImage(BufferedImage image) {
BufferedImage result = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_BYTE_BINARY);
Graphics2D graphics = result.createGraphics();
graphics.drawImage(image, 0,0,Color.WHITE,null);
graphics.dispose();
return result;
}
private BufferedImage rotateImage(BufferedImage image) {
double rads = Math.toRadians(90);
double sin = Math.abs(Math.sin(rads));
double cos = Math.abs(Math.cos(rads));
int width = (int) Math.floor(image.getWidth() * cos + image.getHeight() * sin);
int height = (int) Math.floor(image.getHeight() * cos + image.getWidth() * sin);
BufferedImage rotateImage = new BufferedImage(width, height, image.getType());
AffineTransform affineTransform = new AffineTransform();
affineTransform.translate(width / 2, height / 2);
affineTransform.rotate(rads,0,0);
affineTransform.translate(-image.getWidth() / 2, -image.getHeight() / 2);
AffineTransformOp affineTransformOp = new AffineTransformOp(affineTransform, AffineTransformOp.TYPE_BILINEAR);
affineTransformOp.filter(image,rotateImage);
return rotateImage;
}
@Override
public String getName() {
return "ServletFichier";
}
}
La servlet récupère les deux images ainsi que les deux paramètres extensions, transforme les deux images et les retourne dans un objet JSON.
Composant 4
Ce composant montre l’utilisation de l’API Java présente dans le plugin.jar pour construire une liste YellowBox à partir d’une recherche de société.
Développement
Pour ce qui est de la structure du projet, voici l’arborescence :
- src
- com.yellowbox.yblist
- components
- YbListComponent.java
- servlets
- YbListCellServlet.java
- YbListServerServlet.java
- YbListServerSideBuilderServlet.java
- YbListServlet.java
- utils
- YbListUtils.java
- YbListPlugin.java
- components
- com.yellowbox.yblist
- assets
- index.html
- index.js
- build.properties
- libs
- commons-lang3-3.7.jar
- yellowbox_plugin-3.28.0.jar
- build.xml
Si vous ne disposez pas des fichiers build.properties et build.xml, en voici le contenu :
- build.properties :
buildversion=1.1.0
- build.xml :
<?xml version="1.0" encoding="ISO-8859-1"?>
<project name="Plugin Yellowbox" default="CreateJar" basedir="." xmlns:ivy="antlib:org.apache.ivy.ant">
<property name="src.dir" value="src" />
<property name="build.dir" value="build" />
<property name="classes.dir" value="${build.dir}/classes" />
<property name="lib.dir" value="libs" />
<property name="lib.external" value="external" />
<property name="assets.dir" value="assets" />
<property file = "${assets.dir}/build.properties"/>
<property environment="env"/>
<property name="extension.name" value="yellowbox_test_component-${buildversion}-${env.BUILD_NUMBER}" />
<target name="init">
<mkdir dir="${build.dir}/classes" />
</target>
<target name="build" depends="">
<echo>Compilation des fichiers source</echo>
<antversion property="antversion"/>
<echo>Version ant : ${antversion}</echo>
<javac
srcdir="${src.dir}"
destdir="${classes.dir}"
classpathref="classpath"
excludes= "**test**,test*.java"
debug="on"
encoding="UTF-8"/>
</target>
<path id="classpath">
<pathelement location="${classes.dir}" />
<fileset dir="${lib.dir}">
<include name="**/*.jar" />
</fileset>
<fileset dir="${lib.external}">
<include name="**/*.jar" />
</fileset>
</path>
<target name="clean" description="Removes all the generated files or directories">
<delete dir="${build.dir}" />
</target>
<target name="CreateJar" depends="clean,init,build">
<!-- Copie des dépendances jar(s) -->
<copy todir="${classes.dir}/libs" overwrite="false">
<fileset dir="${lib.dir}" />
</copy>
<!-- Copie des assets -->
<copy todir="${classes.dir}/assets" overwrite="false">
<fileset dir="${assets.dir}" />
</copy>
<jar destfile="${build.dir}/${extension.name}.jar"
basedir="${classes.dir}"
includes="**"
/>
</target>
<property name="ivy.install.version" value="2.4.0" />
<property name="ivy.jar.dir" value="lib" />
<property name="ivy.jar.file" value="lib/ivy-${ivy.install.version}.jar" />
<target name="install-ivy" description="--> install ivy">
<!-- try to load ivy here from local ivy dir, in case the user has not already dropped
it into ant's lib dir (note that the latter copy will always take precedence).
We will not fail as long as local lib dir exists (it may be empty) and
ivy is in at least one of ant's lib dir or the local lib dir. -->
<path id="ivy.lib.path">
<fileset dir="${ivy.jar.dir}" includes="*.jar"/>
</path>
<taskdef resource="org/apache/ivy/ant/antlib.xml"
uri="antlib:org.apache.ivy.ant" classpathref="ivy.lib.path"/>
</target>
<target name="resolve" depends="install-ivy" description="retrieve dependencies with ivy">
<ivy:retrieve type="jar" pattern="${lib.dir}/[artifact]-[revision].[ext]"/>
<ivy:retrieve type="source" pattern="/src-lib/[artifact]-[revision].[ext]"/>
<ivy:report />
<copydir src="locallib" dest="${lib.dir}"></copydir>
</target>
</project>
Frontend
HTML
Prenons le fichier HTML suivant :
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>Yb Objects</title>
<style>
.button_wrapper {
display: flex;
justify-content: space-around;
align-items: center;
flex-wrap: wrap;
}
.button_wrapper button {
min-width: 100px;
max-width: 200px;
}
</style>
</head>
<body>
<main>
<div class="button_wrapper">
<button onclick="fireSearch(0);">Dessiner depuis <strong>drawList</strong></button>
<button onclick="fireSearch(1);">Dessiner depuis <strong>drawListFromServlet</strong></button>
<button onclick="fireSearch(2);">Dessiner en <strong>gestion serveur</strong></button>
</div>
<div id="tab_wrapper">
<table id="result"></table>
</div>
</main>
<script>
</script>
</body>
</html>
Ce fichier contient grossièrement :
- 3 boutons pour lancer le dessin de la liste :
- Un qui lancera la fonction
drawList
en passant0
à la méthodefireSearch
. - Un autre qui lancera
drawListFromServlet
en passant1
. - Le dernier qui dessinera une liste server-side en passant
2
.
- Un qui lancera la fonction
- un tableau pour accueillir la liste.
CSS
On va y ajouter un peu de style dans une balise <style>
dans la balise <head>
:
.button_wrapper {
display: flex;
justify-content: space-around;
align-items: center;
flex-wrap: wrap;
}
.button_wrapper button {
min-width: 100px;
max-width: 200px;
}
JavaScript
Maintenant, on s’interesse au JavaScript dans le fichier index.js. On va d’abord définir une fonction à appeler lorsque les boutons sont cliqués :
function fireSearch(type) {
switch(type) {
case 1:
buildWithServlet();
break;
case 2:
buildServerSide();
break;
default:
buildBasic();
}
}
Avec, en dessous, les différentes fonctions de construction :
function buildWithServlet() {
removeInputSearch();
drawListFromServlet(
"result",
"YbListServlet",
{ "responsive": true, "pageLength": 7 },
() => { console.log('ok'); },
onErrorServlet);
}
function buildServerSide() {
buildInputSearch();
callServlet(
'YbListServerSideBuilderServlet',
{ "responsive": true, "pageLength": 10 }, callBackServletBasic,
onErrorServlet);
}
function buildBasic() {
removeInputSearch();
callServlet(
"YbListServlet",
{ "responsive": false, "pageLength": 5 }, callBackServletBasic,
onErrorServlet);
}
Vous remarquerez que les 2 fonctions, buildBasic()
et buildWithServlet()
, sont assez similaires puisque l’on va toujours appeler YbListServlet. Cela est voulu pour faciliter le développement.
Dans le cas buildBasic()
, on va appeler nous-même la servlet YbListServlet avec callServlet()
et utiliser drawList()
lorsque la servlet nous aura renvoyé son resultat. Pour rappel, voici la signature de la méthode callServlet()
:
/**
* @param servletName {string} - Servlet name
* @param parameters {object} - JSON of parameters
* @param onSuccess {function} - Function to call back on success. Servlet returns a string on success.
* @param onError {function} - Function to call back on error. Servlet returns 3 parameters : jqXHR, textStatus, errorThrown.
*/
function callServlet(servletName, parameters, onSuccess, onError)
Pour cela, on définit la méthode callBackServlet()
:
function callBackServletBasic(res) {
const listData = JSON.parse(res);
if(res?.error) {
displayError(res.error);
}
try {
drawList("result", listData);
} catch (error) {
displayError(error);
}
}
Cette méthode va tout d’abord parser le resultat, qui est une String
, renvoyé par la servlet en JSON. Ensuite, on va vérifier que ce JSON n’a pas de propriété error
(on la définira nous même en Java plus tard) ; auquel cas, on affichera l’erreur grâce à la méthode displayError()
. Finalement, on appelera drawList
pour dessiner la fiche dans la <table id='result'>
prévu à cet effet. On prendra soin d’entourer la méthode d’un try catch
en cas d’erreur.
Voici les méthode qui gère les erreurs :
function onErrorServlet(jqXHR, textStatus, errorThrown) {
displayError(`Status ${jqXHR && jqXHR.status} - ${textStatus} : ${errorThrown}`);
}
function displayError(err) {
if(err) {
if(err.message) {
$('#result').html("Une erreur est survenue : " + err.message);
} else {
$('#result').html("Une erreur est survenue : " + err);
}
} else {
$('#result').html("Une erreur est survenue");
}
}
Dans le cas buildWithServlet()
, on appelera la méthode drawListFromServlet()
. Encore une fois, même s’il s’agit de la même servlet qui est appelée pour construire les données de la liste, on veut juste pouvoir montrer le fonctionnement ici. On fait 1 arrow function pour gérer le onSuccess
avec un simple affichage en console. La méthode drawListFromServlet()
appelera elle-même la méthode drawList()
au succès. Pour rappel, voici le code :
drawListFromServlet(
"result",
"YbListServlet",
{ "responsive": true, "pageLength": 7 },
() => { console.log('ok'); },
onErrorServlet
);
Dans le cas buildServerSide()
, on fait relativement la même chose que dans buildBasic()
. Vous noterez la présense de buildInputSearch()
et removeInputSearch()
. Je ne les mentionne que maintenant car elles serviront à ajouter ou retirer une input pour faire de la recherche dans notre YbList ser-side. Voici leur code :
function getInputSearch() {
return document.getElementById('search');
}
function buildInputSearch() {
if(getInputSearch() != null) {
return;
}
const searchInput = document.createElement("input");
searchInput.id = "search";
searchInput.placeholder = "Raison sociale";
searchInput.type = "text";
searchInput.addEventListener('keydown', (e) => {
if (e.key === "Enter") {
callServlet('YbListServerSideBuilderServlet', { "responsive": true, "pageLength": 10 }, callBackServletBasic, onErrorServlet);
}
});
document.getElementById('tab_wrapper').prepend(searchInput);
}
function removeInputSearch() {
const input = getInputSearch();
if(input == null) {
return;
}
input.remove();
}
Bilan
Au final, vous devriez avoir le fichier HTML suivant :
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>Yb Objects</title>
<style>
.button_wrapper {
display: flex;
justify-content: space-around;
align-items: center;
flex-wrap: wrap;
}
.button_wrapper button {
min-width: 100px;
max-width: 200px;
}
</style>
</head>
<body>
<main>
<div class="button_wrapper">
<button onclick="fireSearch(0);">Dessiner depuis <strong>drawList</strong></button>
<button onclick="fireSearch(1);">Dessiner depuis <strong>drawListFromServlet</strong></button>
<button onclick="fireSearch(2);">Dessiner en <strong>gestion serveur</strong></button>
</div>
<div id="tab_wrapper">
<table id="result"></table>
</div>
</main>
<script>
</script>
</body>
</html>
Ainsi que le fichier Javascript suivant :
function fireSearch(type) {
switch(type) {
case 1:
buildWithServlet();
break;
case 2:
buildServerSide();
break;
default:
buildBasic();
}
}
function buildServerSide() {
buildInputSearch();
callServlet('YbListServerSideBuilderServlet', { "responsive": true, "pageLength": 10 }, callBackServletBasic, onErrorServlet);
}
function getInputSearch() {
return document.getElementById('search');
}
function buildInputSearch() {
if(getInputSearch() != null) {
return;
}
const searchInput = document.createElement("input");
searchInput.id = "search";
searchInput.placeholder = "Raison sociale";
searchInput.type = "text";
searchInput.addEventListener('keydown', (e) => {
if (e.key === "Enter") {
callServlet('YbListServerSideBuilderServlet', { "responsive": true, "pageLength": 10 }, callBackServletBasic, onErrorServlet);
}
});
document.getElementById('tab_wrapper').prepend(searchInput);
}
function removeInputSearch() {
const input = getInputSearch();
if(input == null) {
return;
}
input.remove();
}
function buildWithServlet() {
removeInputSearch();
drawListFromServlet("result", "YbListServlet", { "responsive": true, "pageLength": 7 }, () => { console.log('ok'); }, onErrorServlet);
}
function buildBasic() {
removeInputSearch();
callServlet("YbListServlet", { "responsive": false, "pageLength": 5 }, callBackServletBasic, onErrorServlet);
}
function callBackServletBasic(res) {
const listData = JSON.parse(res);
if(res?.error) {
displayError(res.error);
}
try {
drawList("result", listData);
} catch (error) {
displayError(error);
}
}
function onErrorServlet(jqXHR, textStatus, errorThrown) {
displayError(`Status ${jqXHR && jqXHR.status} - ${textStatus} : ${errorThrown}`);
}
function displayError(err) {
if(err) {
if(err.message) {
$('#result').html("Une erreur est survenue : " + err.message);
} else {
$('#result').html("Une erreur est survenue : " + err);
}
} else {
$('#result').html("Une erreur est survenue");
}
}
function searchValueCallback() {
return $('#search').val();
}
Backend
Côté Java, on va définir 1 composant, 4 servlets et 1 classe utilitaire. Nous allons volontairement omettre la définition du Header
qui ne nous intéresse pas ici (voir autres exemples).
- Le composant, que l’on nommera
YbListComponent.java
, se chargera d’appeler le fichier HTML et de récupérer le paramètre de notre plugin (qui sera l’ID d’une recherche de sociétés). - La 1^ère^ servlet, que l’on nomera
YbListServlet.java
, se chargera de récupérer le résultat de la recherche et de construire la liste. - La 2^ème^ servlet, que l’on nomera
YbListCellServlet.java
, sera appelée dans leonClick
d’une cellule de typeLINK
de notre liste afin de démontrer le fonctionnement duonClick
avec une servlet. - La 3^ème^ servlet, que l’on nomera
YbListServerSideBuilderServlet.java
, sera appelée pour construire notreYbList
server-side. Attention : elle hérite decom.yellowbox.plugin.v3.YbListBuilderServlet
. - La 4^ème^ servlet, que l’on nomera
YbListServerServlet.java
, sera appelée pour gérer le contenu de notreYbList
server-side. Attention : elle hérite decom.yellowbox.plugin.v3.YbListUpdateServlet
. - La classe utilitaire,
YbListUtils
, permet de factoriser le code final. Elle gère la génération des colonnes et des données.
Créez ces fichiers nous allons les remplir dans la suite de cet exemple. Prenez soin de bien faire hériter les classes associées à chaque type de classe (i.e. com.yellowbox.plugin.v3.Component
, com.yellowbox.plugin.v3.Servlet
, …).
YbListComponent
On commence avec le composant. Nous ne nous attarderons pas dessus car celui-ci est assez simple :
package com.yellowbox.plugin.yblist.components;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.yellowbox.plugin.v3.Component;
import com.yellowbox.plugin.v3.Param;
import com.yellowbox.plugin.v3.ParamServlet;
import com.yellowbox.plugin.v3.Params;
import com.yellowbox.plugin.v3.Servlet;
import com.yellowbox.plugin.v3.Param.InOut;
import com.yellowbox.plugin.v3.Param.Type;
import com.yellowbox.plugin.v3.ui.UIRoot;
import com.yellowbox.plugin.yblist.servlets.YbListCellServlet;
import com.yellowbox.plugin.yblist.servlets.YbListServerServlet;
import com.yellowbox.plugin.yblist.servlets.YbListServerSideBuilderServlet;
import com.yellowbox.plugin.yblist.servlets.YbListServlet;
public class YbListComponent extends Component {
public static final String IDRECHERCHE = "IDRECHERCHE";
@Override
public String getHtml() {
return getRessourceAsString("/yblist/index.html");
}
@Override
public List<Class<? extends Servlet>> getServlets() {
List<Class<? extends Servlet>> listServlet = new ArrayList<>();
listServlet.add(YbListServlet.class);
listServlet.add(YbListCellServlet.class);
listServlet.add(YbListServerSideBuilderServlet.class);
listServlet.add(YbListServerServlet.class);
return listServlet;
}
@Override
public List<String> getAssets() {
return Collections.emptyList();
}
@Override
public List<String> getListCss() {
return Collections.emptyList();
}
@Override
public List<String> getListJs() {
List<String> listJs = new ArrayList<>();
listJs.add("/yblist/index.js");
return listJs;
}
@Override
public String getDescription() {
return "Exemple d'utilisation de la YbList";
}
@Override
public String getName() {
return "YbListComponent";
}
@Override
public UIRoot getParamInstanceUI() {
return null;
}
@Override
public List<Class<? extends ParamServlet>> getParamServlets() throws AbstractMethodError {
return Collections.emptyList();
}
@Override
public Params getParams() {
Params parameters = new Params();
Param paramTemp = parameters.addNew(Type.IDRECHERCHE, InOut.IN, "Choisir une recherche sur les societes", IDRECHERCHE);
paramTemp.setMandatory(true);
return parameters;
}
@Override
public String getTitle() {
return getName();
}
}
Prenez soin de bien vérifier le chemin vers le fichier HTML si jamais votre arborescence est différente de celle de l’exemple.
YbListUtils
La classe utilitaire, le coeur du métier. Voici le code de base :
public class YbListUtils {
public static final String ID_FIELD_RS = "107";
public static final String ID_FIELD_ACT = "261";
private YellowboxEntryPoint entryPoint;
public YbListUtils(YellowboxEntryPoint entryPoint) {
this.entryPoint = entryPoint;
}
}
Nous y mettrons les IDs des champs de société qui nous interesse, ici, la raison sociale et l’activité. On mettra l’entry point en attribut pour avoir accès aux services de YellowBox.
Définissons d’abord une méthode qui va construire nos colonnes. Nous allons créer 3 colonnes :
- La 1^ère^ de type
BUTTON
, qui s’appelera “Lien”. Son ID sera simplement “0”. - La 2^ème^ de type
LINK
, qui aura le nom du champ raison sociale. Son ID sera “raison_sociale”. - La 3^ème^ de type
RAW
, qui aura le nom du champ activité. Son ID sera “activite”.
public List<YbColumn> buildColumns() {
List<YbColumn> columns = new ArrayList<>();
columns.add(new YbColumn("0", Type.BUTTON, "Lien"));
Field field;
String id;
Type type;
for(String idChamp : Arrays.asList(ID_FIELD_RS, ID_FIELD_ACT)) {
field = entryPoint.getServices().getFieldService().getFieldById(idChamp);
id = ID_FIELD_RS.equals(idChamp) ? "raison_sociale" : "activite";
type = field.getId().equals(ID_FIELD_RS) ? Type.LINK : Type.RAW;
columns.add(new YbColumn(id, type, field.getName()));
}
return columns;
}
Ceci étant fait, on va créer une méthode qui va venir récupérer le résultat de la recherche avec les champs voulus :
public Page<Record> getRecordsFromSearch(String searchId) {
List<Field> fields = new ArrayList<>();
fields.add(new Field(ID_FIELD_RS, "", ""));
fields.add(new Field(ID_FIELD_ACT, "", ""));
QueryPage query = new QueryPage();
query.setIdSearch(searchId);
query.setFields(fields);
return entryPoint.getServices().getRecordServiceV3().getRecordsBySearch(query);
}
On poursuit avec la construction des données. On définit encore une fois une méthode qui s’en chargera :
public List<Map<String, YbCell>> buildData(List<Record> records) {
List<Map<String, YbCell>> data = new ArrayList<>();
Map<String, YbCell> row;
YbCell cell;
for (Record societe : records) {
row = new HashMap<>();
cell = new YbButtonCell("() => { window.open('https://www.google.com', '_blank').focus(); }", "https://upload.wikimedia.org/wikipedia/commons/6/6d/Windows_Settings_app_icon.png");
row.put("0", cell);
for(Value val: societe.getValues()) {
if(!val.getField().getId().equals(ID_FIELD_ACT) && !val.getField().getId().equals(ID_FIELD_RS)) {
continue;
}
if(val.getField().getId().equals(ID_FIELD_ACT)) {
cell = new YbRawCell(val.getValue());
row.put("activite", cell);
} else if(val.getField().getId().equals(ID_FIELD_RS)) {
cell = new YbLinkCell(val.getValue(), "() => { callServlet('YbListCellServlet', { 'param1': 'val1', 'param2': 'val2' }, (res) => { alert(res); }, (err) => { console.error(err); }); }");
row.put("raison_sociale", cell);
}
}
data.add(row);
}
return data;
}
On fournit en paramètre de la méthode le résultat de la recherche. Le but va être de boucler sur cette liste de Record
et extraire les données qui nous intéresse pour construire nos cellules.
Ainsi, dans notre boucle, on instancie une nouvelle ligne en premier lieu. Ensuite, on définit la première cellule, qui est supposé être un bouton. On lui passe 2 paramètres :
- le
onClick
, sous forme d’arrow function au formatString
. On ouvrira simplement un nouvel onglet dans le navigateur de l’utilisateur qui redirige vers Google. - le
imgUrl
, qui est simplement l’URL d’une image de molette.
On ajoute notre cellule bouton à la ligne en précisant l’ID de la colonne à laquelle elle appartient (ici, “0”).
Pour la suite, on boucle sur les valeurs contenu dans le Record
. On s’assure qu’il s’agit soit du champ raison sociale ou activité.
- Si c’est le champ activité, on définit une cellule de type
RAW
et sonlabel
sera la valeur associée au champ activité. - Si c’est le champ raison sociale, on définit une cellule de type
LINK
. Sonlabel
sera la valeur associée au champ raison sociale et sononClick
appelera notre fameuse servletYbListCellServlet
avec une série de paramètres très simples. On fait un simplealert()
du résultat pour leonSuccess
ducallServlet()
et un simpleconsole.error()
pour leonError
.
Enfin, on pense à ajouter chaque cellule à la ligne avec le bon ID de colonne.
Finalement, on ajoute chaque ligne à data
et on retourne le resultat.
Voici le fichier obtenu :
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.yellowbox.plugin.v3.YellowboxEntryPoint;
import com.yellowbox.plugin.v3.ybobjects.yblist.ybcell.YbButtonCell;
import com.yellowbox.plugin.v3.ybobjects.yblist.ybcell.YbCell;
import com.yellowbox.plugin.v3.ybobjects.yblist.ybcell.YbLinkCell;
import com.yellowbox.plugin.v3.ybobjects.yblist.ybcell.YbRawCell;
import com.yellowbox.plugin.v3.ybobjects.yblist.ybcolumn.YbColumn;
import com.yellowbox.plugin.v3.ybobjects.yblist.ybcolumn.YbColumn.Type;
import com.yellowbox.ws.beans.Field;
import com.yellowbox.ws.beans.Page;
import com.yellowbox.ws.beans.QueryPage;
import com.yellowbox.ws.beans.Record;
import com.yellowbox.ws.beans.Value;
public class YbListUtils {
public static final String ID_FIELD_RS = "107";
public static final String ID_FIELD_ACT = "261";
private YellowboxEntryPoint entryPoint;
public YbListUtils(YellowboxEntryPoint entryPoint) {
this.entryPoint = entryPoint;
}
public List<YbColumn> buildColumns() {
List<YbColumn> columns = new ArrayList<>();
columns.add(new YbColumn("0", Type.BUTTON, "Lien"));
Field field;
String id;
Type type;
for(String idChamp : Arrays.asList(ID_FIELD_RS, ID_FIELD_ACT)) {
field = entryPoint.getServices().getFieldService().getFieldById(idChamp);
id = ID_FIELD_RS.equals(idChamp) ? "raison_sociale" : "activite";
type = field.getId().equals(ID_FIELD_RS) ? Type.LINK : Type.RAW;
columns.add(new YbColumn(id, type, field.getName()));
}
return columns;
}
public List<Map<String, YbCell>> buildData(List<Record> records) {
List<Map<String, YbCell>> data = new ArrayList<>();
Map<String, YbCell> row;
YbCell cell;
for (Record societe : records) {
row = new HashMap<>();
cell = new YbButtonCell("() => { window.open('https://www.google.com', '_blank').focus(); }", "https://upload.wikimedia.org/wikipedia/commons/6/6d/Windows_Settings_app_icon.png");
row.put("0", cell);
for(Value val: societe.getValues()) {
if(!val.getField().getId().equals(ID_FIELD_ACT) && !val.getField().getId().equals(ID_FIELD_RS)) {
continue;
}
if(val.getField().getId().equals(ID_FIELD_ACT)) {
cell = new YbRawCell(val.getValue());
row.put("activite", cell);
} else if(val.getField().getId().equals(ID_FIELD_RS)) {
cell = new YbLinkCell(val.getValue(), "() => { callServlet('YbListCellServlet', { 'param1': 'val1', 'param2': 'val2' }, (res) => { alert(res); }, (err) => { console.error(err); }); }");
row.put("raison_sociale", cell);
}
}
data.add(row);
}
return data;
}
public Page<Record> getRecordsFromSearch(String searchId) {
List<Field> fields = new ArrayList<>();
fields.add(new Field(ID_FIELD_RS, "", ""));
fields.add(new Field(ID_FIELD_ACT, "", ""));
QueryPage query = new QueryPage();
query.setIdSearch(searchId);
query.setFields(fields);
return entryPoint.getServices().getRecordServiceV3().getRecordsBySearch(query);
}
}
YbListCellServlet
Voyons maintenant la servlet la plus simple, qui, pour rappel, servira à être appelée lors du clic sur une cellule de type LINK
:
import java.util.stream.Collectors;
import com.yellowbox.plugin.v3.ParamValues;
import com.yellowbox.plugin.v3.Servlet;
public class YbListCellServlet extends Servlet {
@Override
public String call(ParamValues arg0) {
return "Appel de la servlet '" + getName() + "' avec les arguments : " + arg0.getValues().stream().map(paramVal -> paramVal.getVarName() + " = " + paramVal.getString()).collect(Collectors.toList());
}
@Override
public String getName() {
return "YbListCellServlet";
}
}
Ici encore, rien de très compliqué. On se charge juste de renvoyer du texte contenant le nom de la servlet avec ses arguments passés en paramètres.
YbListServlet
La servlet de construction de la liste :
import java.util.List;
import java.util.Map;
import com.yellowbox.plugin.v3.ParamValues;
import com.yellowbox.plugin.v3.Servlet;
import com.yellowbox.plugin.v3.ybobjects.yblist.YbList;
import com.yellowbox.plugin.v3.ybobjects.yblist.YbListException;
import com.yellowbox.plugin.v3.ybobjects.yblist.ybcell.YbCell;
import com.yellowbox.plugin.v3.ybobjects.yblist.ybcolumn.YbColumn;
import com.yellowbox.plugin.yblist.components.YbListComponent;
import com.yellowbox.plugin.yblist.utils.YbListUtils;
import com.yellowbox.ws.beans.Page;
import com.yellowbox.ws.beans.Record;
public class YbListServlet extends Servlet {
@Override
public String call(ParamValues params) {
YbListUtils utils = new YbListUtils(getYellowboxEntryPoint());
String searchId = getComponent().getParamValues().getStringValue(YbListComponent.IDRECHERCHE);
Page<Record> records = utils.getRecordsFromSearch(searchId);
List<YbColumn> columns = utils.buildColumns();
List<Map<String, YbCell>> data = utils.buildData(records.getContent());
YbList ybList = new YbList(columns, data);
ybList.setResponsive(Boolean.parseBoolean(params.getStringValue("responsive")));
ybList.setPageLength(Integer.parseInt(params.getStringValue("pageLength")));
try {
return ybList.toJson();
} catch (YbListException e) {
return "{ 'error': '" + e.getMessage() + "' }";
}
}
@Override
public String getName() {
return "YbListServlet";
}
}
On commence par appeler la méthode utilitaire getRecordsFromSearch()
pour récupérer les données de la recherche. Ensuite, on définit les colonnes avec la méthode utilitaire buildColumns()
et on fait de même avec les données grâce à buildData()
. On instancie une YbList
et on appelle la méthode toJson()
.
Attention à bien entourer la méthode
toJson()
d’untry catch
en cas d’erreur.
YbListServerSideBuilderServlet
Attardons nous maintenans sur la servlet de construction de la YbList server-side :
import java.util.List;
import com.yellowbox.plugin.v3.ParamValues;
import com.yellowbox.plugin.v3.ybobjects.yblist.YbList;
import com.yellowbox.plugin.v3.ybobjects.yblist.ybcolumn.YbColumn;
import com.yellowbox.plugin.v3.ybobjects.yblist.ybserverdata.servlets.YbListBuilderServlet;
import com.yellowbox.plugin.yblist.utils.YbListUtils;
public class YbListServerSideBuilderServlet extends YbListBuilderServlet {
@Override
public YbList build(ParamValues params) {
YbListUtils utils = new YbListUtils(getYellowboxEntryPoint());
List<YbColumn> columns = utils.buildColumns();
YbList ybList = new YbList(columns, YbListServerServlet.class.getSimpleName());
ybList.setResponsive(Boolean.parseBoolean(params.getStringValue("responsive")));
ybList.setPageLength(Integer.parseInt(params.getStringValue("pageLength")));
ybList.setServerSide(true);
ybList.setSearchValueJsCallbackName("searchValueCallback");
return ybList;
}
@Override
public String getName() {
return getClass().getSimpleName();
}
}
Le code est assez similaire à celui de la Servlet précédente en ce qui concerne la construction de la liste, hormis que l’on ne va pas fournir de données à notre instance de YbList
. En effet, on va lui fournir un nom de YbListUpdateServlet
cette fois-ci (décrite à la section suivante). On précise que la liste sera server-side et on lui précise qu’il y aura une méthode Javascript qui lui fournira une valeur à chercher quand la liste se mettra à jour.
Vous remarquerez que cette Servlet retourne un YbList
et pas un String
.
YbListServerServlet
Partie importante, la gestion des données côté serveur de la liste :
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import com.yellowbox.plugin.v3.ybobjects.yblist.ybcell.YbCell;
import com.yellowbox.plugin.v3.ybobjects.yblist.ybserverdata.YbListServerData;
import com.yellowbox.plugin.v3.ybobjects.yblist.ybserverdata.servlets.YbListUpdateServlet;
import com.yellowbox.plugin.v3.ybobjects.yblist.ybserverdata.servlets.params.YbListUpdateServletParams;
import com.yellowbox.plugin.yblist.components.YbListComponent;
import com.yellowbox.plugin.yblist.utils.YbListUtils;
import com.yellowbox.ws.beans.Page;
import com.yellowbox.ws.beans.Record;
import com.yellowbox.ws.beans.Value;
public class YbListServerServlet extends YbListUpdateServlet {
@Override
public YbListServerData update(YbListUpdateServletParams params) {
YbListUtils utils = new YbListUtils(getYellowboxEntryPoint());
String searchId = getComponent().getParamValues().getStringValue(YbListComponent.IDRECHERCHE);
Page<Record> recordPage = utils.getRecordsFromSearch(searchId);
int start = params.getStart();
int length = params.getLength();
String search = params.getSearch().getValue();
List<Record> records = recordPage.getContent().stream()
.filter(r -> r.getValues().stream()
.filter(v -> v.getField().getId().equals(YbListUtils.ID_FIELD_RS))
.findAny()
.orElse(new Value())
.getValue().toLowerCase().contains(search.toLowerCase()))
.collect(Collectors.toList());
List<Record> subList = records.subList(start, (start + length > records.size() ? ((int) records.size()) : start + length));
List<Map<String, YbCell>> data = utils.buildData(subList);
int draw = params.getDraw();
return new YbListServerData(data, draw, recordPage.getContent().size(), records.size());
}
@Override
public String getName() {
return getClass().getSimpleName();
}
}
L’idée ici est de renvoyer les données demandées par la liste. Les paramètres de cette demande sont présentés dans l’objet YbListUpdateServletParams
passé en paramètre de la méthode update()
. Le but est que nous renvoyons un YbListServerData
qui contient les élément de la page demandée :
- les données, sous la forme classique d’une
List<Map<String, YbCell>>
- le
draw
, élément obligatoire pour vérifier l’authenticité des données (voir ici) - le nombre total de données : en tout, pas que dans la page demandée
- le nombre de données filtrées : dans la même idée que la précédente, le nombre total d’élément après application du filtre
Dans un premier temps, on récupère les 3 informations suivantes :
- start : à quelle ligne commence la demande
- length : le nombre de ligne par page
- search : la valeur à rechercher
Avec ces 3 informations, on va gérer l’application du filtre sur le total des données récupérées de la recherche grâce à l’élément search
. Ainsi, on obtient une première liste filtrée. De cette liste, on va en tirer une sous-liste qui elle va représenter les données à afficher dans la page demandée grâce aux éléments start
et length
. On convertit cette sous-liste grâce à notre méthode utilitaire buildData()
. Finalement, on contruit notre YbListServerData
et on le retourne.
Notre backend est terminé ! Vous n’avez plus qu’à compiler le plugin en lançant le build.xml
.
Paramétrage
Une fois le plugin installé sur votre YellowBox, la paramétrage est assez simple.
Les noms de plugin et de package sont différents mais il s’agit du même plugin que nous avons construit
Utilisation
Affichons le plugin dans l’accueil depuis le mode éditeur dans le 1^er^ onglet. Pensez à rdimensionner votre plugin pour un meilleur affichage.
Cliquez sur l’un des bouton pour afficher la liste. Voyez que les 2 affiche la même liste mais n’utilise pas la même méthode JavaScript.
Code source
Le code source est disponible ici sous la forme d’une archive, importable dans l’IDE Eclipse.