Développement

[SP2013] – Récupérer le statut d’un Workflow d’Approbation en REST

Posted on

Bonjour,

Aujourd’hui, un article orienté développement. Il s’agit ici d’une fonction JavaScript permettant de récupérer le statut d’un Workflow d’approbation standard SharePoint Server 2013 afin de l’afficher dans une page par exemple.

A la base, dans mon exemple, je devais afficher ce statut d’approbation dans la page d’accueil d’un Document Set sur lequel portait ce flux d’approbation. L’idée était d’insérer, via un WebPart  éditeur de contenu, un bloc de HTML et de fonctions Javascript afin d’afficher ce statut.

Je ne vais détailler ici que la partie requêtage, il sera facile pour vous d’utiliser ceci dans une logique métier par exemple.

1/ Statuts du Flux de travail d’approbation

Première chose à connaitre, ce sont les différents statuts qui peuvent être affichés lors de l’utilisation de ce type de flux de travail. Ces statuts sont représentés par un entier :


0 == Not Started
1 == Failed on Start
2 == In Progress
3 == Error Occurred
4 == Canceled
5 == Completed
6 == Failed on Start(Retrying)
7 == Error Occurred (Retrying)
15 == Canceled
16 == Approved
17 == Rejected

 

2/ Appeler la fonction…

Pour appeler la fonction, j’ai choisi d’utiliser la fonction JQuery ready(), qui appelle ExecuteOrDelayUntilScriptLoaded() afin de différer l’appel à ma fonction qui récupère le statut, après le chargement de sp.js :


$(document).ready(function () { ExecuteOrDelayUntilScriptLoaded(loadItem, "sp.js"); });

Puis on déclare la fonction loadItem. Cette fonction récupère l’identifiant de l’item courant dans la QueryString (vous pouvez modifier cette partie suivant le cas d’usage) puis construit l’url de la requête REST à partir de cet identifiant et de l’identifiant de la Liste :

function loadItem() {
itemId = getParameterByName('ID');
console.log(WFName + ' : Current item id = ' + itemId);
listId = _spPageContextInfo.pageListId;
console.log(WFName + ' : Current list id = ' + listId);
var url = _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getById('" + listId + "')/items(" + itemId + ")?$expand=FieldValuesAsHtml";
var requestHeaders = { "accept" : "application/json;odata=verbose" };
return $.ajax({
url: url,
method: "GET",
contentType: "application/json;odata=verbose",
headers: requestHeaders,
success : loaditemsuccess,
error : onError
});
}

 

Suite à l’exécution de la fonction précédente, en cas de succès, on récupère le statut du Workflow et on affiche le statut comme souhaité (non détaillé dans cet article) :


var WFColunmName = 'Validati';

var WFName = 'WF Approbation';

function loaditemsuccess(data, request) {
var moderationStatusName = data.d.FieldValuesAsHtml[WFColumnName];
console.log(WFName + ' : Moderation Status = ' + moderationStatusName);

//Change DOM into div "WFStatus" to add link to start workflow + icon
if(moderationStatusName == 'Canceled') {
//Display link to start a new worklow
displayStartNewWorkflow('Workflow has been canceled. ','');
}
else if(moderationStatusName == 'Rejected') {
//Display link to start a new worklow
displayStartNewWorkflow('Workflow has been rejected. ','');
}
else if(moderationStatusName == 'Approved') {
//Document set is already approved, display a special icon with no link
displayWorkflowApproved();
}
else if(moderationStatusName == 'In Progress') {
displayWorkflowInProgress();
}
else if (moderationStatusName == '') {
//Display link to start a new worklow
displayStartNewWorkflow('','');
}
else {
onError('Unable to load workflow status.');
}
}

 

Pour récupérer le statut du Workflow, on s’appuie donc sur la colonne de statut créée par l’association du flux d’approbation à la liste/bibliothèque. C’est donc plutôt simple, il suffit d’une requête REST (que je commence à préférer au JSOM ! 🙂 )

 

3/ Quelques fonctions utiles

Voici quelques fonctions que j’ai utilisé dans mon code :


//Function to get a parameter value from QueryString
function getParameterByName(name) {
name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
var regexS = "[\\?&]" + name + "=([^&#]*)";
var regex = new RegExp(regexS);
var results = regex.exec(window.location.search);
if(results == null){
return "";
}
else{
return decodeURIComponent(results[1].replace(/\+/g, " "));
}
}
 

//Function to manage errors
function onError(error) {
console.log('An error occured:' + error);
}

 

Et voilà !

Advertisements

[SP2013] – implémenter un JSLink dans les Annonces – Afficher le contrôle Like

Posted on Updated on

Bonjour à tous,

Cette fois-ci je vais vous parler d’une personnalisation de SharePoint 2013 au travers des JSLink. Ces JSLink sont apparus avec SharePoint 2013 et permettent de personnaliser les affichages des données à plusieurs niveaux grâce à ce que l’on appelle le Client-Side Rendering (CSR). Vous pourrez ainsi personnaliser :

  • Les Vues (List View)
  • Les Formulaires (dispform, editform)
  • Les champs (SPFields)

Il s’agit donc ici de contrôler la manière dont ces différents éléments seront rendus dans les pages SharePoint à l’aide de JavaScript principalement. Je ne serai volontairement pas exhaustif dans cet article car il me faudrait beaucoup de temps et cela me laissera peut-être l’opportunité d’écrire de nouveaux articles.

Voici donc mon périmètre : proposer un bouton Like/Unlike dans le formulaire d’affichage d’une actualité (Dispform de la liste Announcements (TemplateID = 104) et ceci sans recourir au déploiement d’une solution SharePoint WSP, mais en réalisant les opérations seulement coté “client”, avec des droits Administrateur de site (pas de ferme).

L’idée est donc de produire et déployer un fichier JavaScript dans SharePoint et de l’utiliser en l’associant à mon formulaire Dispform, sans perdre de fonctionnalités natives ou brider le comportement de ma liste, formulaires etc.

 

1/ Activer le Like sur la liste d’Announcements (Annonces)

Rien de très compliqué ici. La plupart des administrateurs fonctionnels ou techniques de sites SharePoint sauront réaliser l’opération. Pour cela, il faut naviguer jusqu’à la liste en question, et aller dans ses Paramètres. Dans cette page, nous allons dans le menu Paramètres d’évaluation (Rating Settings) :

01

Puis d’autoriser les évaluations de type “Like” :

  1. Autoriser les éléments à être évalués (traduction approximative…) : Oui
  2. Quel type d’évaluation : Likes

02

Et bien sûr, nous validons la page avec le bouton “OK”.

 

2/ Activer le JSLink sur le formulaire Dispform de la liste Announcements

En effet, une phase de préparation est nécessaire. Les formulaires (Dispform, Editform) ne proposent pas directement le paramètres JSLink sur le composant WebPart positionné dans ces pages. C’est bien ce composant WebPart qui est chargé d’afficher le formulaire avec ses champs (entêtes et contrôles de rendu).

Pour cela, il faut se rendre dans les paramètres du site > MasterPage Gallery (dans la catégorie Web Designer Galleries) :

03

Pour ma part, je choisis de placer mon JSLink dans le sous-dossier Display Templates mais il est préférable de les placer dans un sous-dossier afin de s’y retrouver à l’avenir…

Ensuite, on Upload un fichier JavaScript presque vide (SharePoint n’accepte pas de télécharger des fichiers vides…), ici appelé “jsnewsdispform.js” :

04

05

On clique sur “OK” pour valider et on renseigne le formulaire suivant. Ici il faudra être précis !

  • Choisir le Content Type : JavaScript Display Template
  • Name : Nom du fichier JavaScript (conservez celui indiqué)
  • Title : Au choix
  • Target Control Type : Form
  • Standalone : Override
  • Target Scope : Url du site où se situe l’Application Announcements (l’url de mon site en l’occurence)
  • Target List Template ID : 104 (Template ID pour les Annonces SharePoint)

 

06

07

On valide en cliquant sur “Save”. Le fichier est enregistré (il faudra penser à le publier en version principale si la gestion des versions est activée) :

08

Copier l’url du document jsnewsdispform.js (url absolue dans un fichier texte, cela nous resservira plus tard).

3/ Modification du Dispform.aspx et ajout du lien vers le fichier JavaScript

Le prérequis sont en place, il nous faut maintenant indiquer au formulaire Dispform.aspx de notre liste d’annonces qu’il doit utiliser l’attribut JSLink… et bien sûr le faire pointer vers notre fichier JavaScript.

Pour cela, naviguez vers l’Application d’annonce créé sur votre site. Si aucune actualité n’est présente, créez en une qui nous servira pour aller sur la page Dispform.aspx.

09

Une fois fait, cliquez sur une actualité. La page s’ouvre, c’est bien la page Dispform.aspx.

10

Puis on passe en mode Edition sur la page. Pour cela, utilisez le menu Site Settings (Paramètres du site) et cliquez sur “Edit page” (Editer la page) :

11

La page se rafraichit, elle est en mode Modification. Dans le coin haut-droit du WebPart, cliquez sur “Edit Web Part” :

12

La boite à outil (Toolbox) du WebPart s’affiche. Dans la catégorie “Miscellaneous” (Divers de mémoire…), le champ JSLink est bien disponible (si vous n’avez pas réalisé les étapes précédentes, il ne sera pas disponible).

Dans ce champ JSLink, entrez (en modifiant l’url si besoin, suivant l’emplacement du fichier jsnewsdispform.js) :

~site/_layouts/15/reputation.js|~site/_catalogs/masterpage/Display%20Templates/jsnewsdispform.js

13

Notez l’emploi de “|” pour demander à SharePoint de charger plusieurs fichiers JavaScript. Et ici nous aurons besoin à la fois du fichier jsnewsdispform.js mais également reputation.js pour gérer les Likes/Unlike.

Validez en cliquant sur “OK” et sauvegardez la page :

14

A partir de ce moment, lorsque que la page dispform sera affichée, notre fichier sera chargé.

4/Implémentation du JSLink

Nous abordons ici l’aspect le plus complexe. Comme je l’ai dit au début de l’article, je ne vais pas détailler l’usage du JSLink, mais je peux déjà vous conseiller la lecture de l’article proposé par Tobias Zimmergren (et l’ensemble de ses excellents articles) ici : http://zimmergren.net/technical/sp-2013-using-the-spfield-jslink-property-to-change-the-way-your-field-is-rendered-in-sharepoint-2013 et également la série d’articles de Martin Hatch ici : http://www.martinhatch.com/2013/08/jslink-and-display-templates-part-1.html.

Ensuite, quelques précisions. L’implémentation JavaScript peut être complexe, et dans l’exemple présent, le code est loin d’être parfait et vous demandera forcément une phase d’optimisation, de refactorisation afin de l’adapter pour de la production. Une fois mes excuses présentées aux grands chefs du JavaScript, voici mon exemple qui fonctionne !

4.1 Charger les éléments nécessaires

Effectivement, pour faire fonctionner la plupart des JSLink il est bon de se documenter et de bien connaitre les Features proposées par SharePoint 2013. En particuler le MDS (Minimal Download Strategy) qui aura un impact fort sur tous vos développements JSLink. Je ne vais pas détailler non plus le pourquoi du comment, il y a des dizaines d’article qui traitent du sujet (Cf. Google & compagnie, en particulier l’article de Wictor Wilén : http://www.wictorwilen.se/the-correct-way-to-execute-javascript-functions-in-sharepoint-2013-mds-enabled-sites).

Pour pouvoir fonctionner avec et sans la fonctionnalité MDS activée, voici ce que l’on peut écrire :


//Load CSR-MDS module if feature is activated
if (typeof _spPageContextInfo != "undefined" && _spPageContextInfo != null) {
    RegisterInMDS();
}
else {
    RegisterLikes();
}

//Register CSR-MDS module then call template registration
function RegisterInMDS() {
    // MDS enabled site
    RegisterModuleInit(_spPageContextInfo.siteServerRelativeUrl + "/_catalogs/masterpage/Display%20Templates/jsnewsdispform.js", RegisterLikes);
    // MDS disabled site
    RegisterLikes();
}

4.2 Afficher les contrôles

Puis on appelle la fonction RegisterLikes() qui se charge d’indiquer qu’un nouveau Template de rendu est associé au champ “LikesCount”, pour le formulaire DisplayForm. J’ajoute également une ligne qui permettra de charger JQuery que j’utiliserai dans ma solution (s’il n’est pas déjà présent).
Je demande donc ici d’appeler la fonction LikeFieldViewTemplate lorsque le champ LikesCount est appelé dans un formulaire de type Dispform :


//Register Template for Number Of Like Field
function RegisterLikes () {

   // Loading jQuery from a CDN path if the local is unavailable
   (window.jQuery || document.write("< script src="//ajax.aspnetcdn.com/ajax/jquery/jquery-1.10.0.min.js">< /script>"));
   var likeFieldCtx = {};

   likeFieldCtx.Templates = {};   
   likeFieldCtx.Templates.Fields = {"LikesCount": {
	"DisplayForm": LikeFieldViewTemplate
   }};

   SPClientTemplates.TemplateManager.RegisterTemplateOverrides(likeFieldCtx);
}

NB : j’aurai également dû, et c’est la bonne pratique, déclarer un namespace pour rassembler mes fonctions.

Puis on déclare la fonction qui se chargera du rendu :


//Display Number Of Like field with custom format
function LikeFieldViewTemplate(ctx) { 
	
   var likeFieldcontext = SPClientTemplates.Utility.GetFormContextForCurrentField(ctx);
	
   ExecuteOrDelayUntilScriptLoaded(function() { GetItemData(ctx, ctx.FormContext, likeFieldcontext.fieldName); },"sp.js");
	
}

Ici je récupère le contexte associé au formulaire courant. Vous trouverez sur le net plusieurs façon de réaliser cela, mais dans le contexte du Dispform, c’est la seule qui a fonctionné pour moi : utiliser SPClientTemplates.Utility.GetFormContextForCurrentField(ctx). Ensuite j’appelle la fonction GetItemData mais après que sp.js soit chargé (ExecuteOrDelayUntilScriptLoaded).

Cette fonction GetItemData permet de charger le contexte dont j’aurai besoin, en particulier les Fields qui ne sont pas affichés dans le formulaire (de manière générale, s’ils ne sont pas affichés, ils ne sont pas chargé). Hors j’ai besoin du champ “LikedBy” pour afficher le nom des personnes qui ont déjà liké cet élément.

On charge donc ici l’item (colonnes : LikedBy, ID, LikesCount et Title) et pour cela on utilise la fonction load() et executeQueryAsync() en JSOM à partir de l’ID de l’item :


function GetItemData(ctx, formContext, likeFieldName) {

   var itemId = formContext.itemAttributes.Id;
   var listId = formContext.listAttributes.Id;

   var clientContext = SP.ClientContext.get_current();
   var list = clientContext.get_web().get_lists().getById(listId);
   clientContext.load(list, 'Title', 'Id');
   var item = list.getItemById(itemId);
   clientContext.load(item, "LikedBy", "ID", "LikesCount", "Title");
   clientContext.executeQueryAsync(
      Function.createDelegate(this, 
            function(){onLoadItemDataSucceeded(item, listId, ctx, likeFieldName);}),
            Function.createDelegate(this, onLoadItemDataFailed));
}

La fonction “onLoadItemDataSucceeded()” appelée en cas de succès du chargement de l’item permet d’afficher les contrôles : smiley + bouton Like/Unlike dans la page. Ce bouton appellera une fonction JavaScript personnalisée qui se chargera d’appeler l’API sociale de SharePoint.
On stocke le html nécessaire dans une variable htmlresult. Cet HTML sera injecté grâce à JQuery dans la page.

Dans ce premier extrait de la fonction onLoadItemDataSucceeded, le but est de récupérer le nom de toutes les personnes ayant liké l’élément avec une limite imposée à 5 noms et de placer “You” en début de liste si l’utilisateur connecté l’a déjà liké. Par ailleurs on peut arriver à 6 noms si l’utilisateur connecté arrive en dernier. Mais pour moi, cela n’est pas un problème. Le fait d’indiquer que l’utilisateur connecté a déjà liké prévaut sur le nombre de “likers”.



function onLoadItemDataSucceeded(item, listId, ctx, likeFieldName) {
   var likesCount = item.get_item('LikesCount');
   var itemId = item.get_item('ID')
   var likedBy = null;
   var likers = [];
   var likerslist = '';
   var userAlreadyLike = false;
   var currentUserId = _spPageContextInfo.userId;
   var linkerslimit = 5;
   var linkerslimitOverpass = false;
	
   var htmlresult = "";
	
   //Get likes & likers on current item
   if(likesCount != 0) {
      var likedBy = item.get_item("LikedBy");
		
      if (!SP.ScriptHelpers.isNullOrUndefined(likedBy)) {
	
         for(var i=0; i < likedBy.length; i++) {
            likers[likers.length] = {
               id: String(likedBy[i].get_lookupId())
               , title: likedBy[i].get_lookupValue()
            };
            //If user in loop is current user, then display You on first row of the tooltip
            if(likers[i].id == currentUserId) {
               userAlreadyLike = true;
               likerslist = 'You' + likerslist;
            }	
            else if(i < linkerslimit){				
               likerslist += likers[i].title + "\\r";				
         }
         else linkerslimitOverpass = true;
      }
      if(linkerslimitOverpass == true) {
         //If limit of likers is overpass, then display "..." at the end of the tooltip
         likerslist += '...';
      }
   }
   else {
      likesCount = 0;
   }
}
else {
   likesCount = 0;
}   

Ce second extrait construit le HTML et l’injecte via JQuery. Le but ici est de modifier l’entête de la colonne qui est “Number of likes” par “Do you like it?” puis de remplacer l’espace vide par le fameux smiley (au survol on consulte les noms des personnes qui ont liké) et le bouton Like/Unlike :



//Change DOM of page to insert Like controls
//Get td with column name : SPBookmark_LikesCount
var headerAnchorName = 'SPBookmark_' + likeFieldName;
var fieldid = '#SPFieldLikes';
	
//get td with column header => change text
$('a[name=' + headerAnchorName + ']').parent('h3').text("If you like it");
	
//get td with field value => add controls to like/unlike etc.
//add like control with smiley, number of likes, etc.
if(likesCount == 0 || userAlreadyLike == false) {

   htmlresult += 'Like';

}
else if (userAlreadyLike == true) {
   
   htmlresult += 'Unlike';

}

   //clear field content then insert buttons, icons etc.
   $(fieldid).empty();
   $(fieldid).append(htmlresult);
}

//Function called if refresh data on like/unlike controls fails
function onLoadItemDataFailed(sender, args) {
   alert('ERROR !');
   console.log('Error when loading item data.');
}

4.3 Gérer les likes/unlikes

Enfin, il faut développer les fonctions qui gèreront les clics utilisateur sur le bouton Like/Unlike. Pour cela, il faut s’assurer que “reputation.js” soit bien chargé puis appeler la fonction “Microsoft.Office.Server.ReputationModel.Reputation.setLike()” sans oublier executeQueryAsync() pour exécuter la requête :


//set like or unlike on selected item when current user clicks on like / unlike button
function setLikeOnNews(listId, itemId, like, itemtitle) { 

   var clientContext = new SP.ClientContext();
   //register native JS for social capabilities	
   //set like. If like == true => Like item ; if like == false => Unlike item	
   EnsureScriptFunc('reputation.js', 'Microsoft.Office.Server.ReputationModel.Reputation',
      function () {                     
         Microsoft.Office.Server.ReputationModel.Reputation.setLike(clientContext,listId,itemId, like);
  
         clientContext.executeQueryAsync(
               Function.createDelegate(this, function(){onQuerySucceeded(listId, itemId, like, itemtitle);}),
	       Function.createDelegate(this, onQueryFailed));
       }
   );
}

4.4 Afficher une notification

Petit bonus, j’ai choisi d’afficher une popup non-persistante afin d’avoir un retour utilisateur et confirmer que le like/unlike a bien été pris en compte. Pour cela :


//If like function is ok (without error)
function onQuerySucceeded(listId, itemId, like, itemtitle) {
   //Display a notification and change text on like/unlike link
   var message = "";
   if(like == "true") {
      var message ="You like the item";
   }
   else if(like == "false") {
      var message ="You unlike the item ";
   }
   else {
      var message ="An error occurred. Please try again";
   }
	
   //Display a popup on top right corner to confirm like / unlike
   var extraData = new SPStatusNotificationData(
   STSHtmlEncode(''),
   STSHtmlEncode(decodeURIComponent(itemtitle)),
   _spPageContextInfo.webLogoUrl
   );
 
   var myNotification = new SP.UI.Notify.Notification(
   SPNotifications.ContainerID.Status,
   STSHtmlEncode(message),
   false,
   '',
   function () { },
    extraData
   );
 
   myNotification.Show();
	
   //refresh data on tooltip (likers, number of likes)
   refreshLikeData(listId, itemId, like, itemtitle);	
}

//If like function throws an error
function onQueryFailed(sender, args) {
   //display an error
   var message = "An error occurred when you try yo like the item. : " + args.get_message();
   var notifyId = SP.UI.Notify.addNotification(message, false);
}

4.5 Mettre à jour les contrôles après un like/unlike

Bien sûr, après que l’utilisateur ait liké/déliké (anglicisme… quand tu nous tiens), il convient de rafraichir les contrôles affichés afin d’incrémenter/décrémenter le nombre de likes, la liste des personnes ayant liké et également modifier le bouton pour pouvoir déliké si on a liké et inversement.

Pour cela, il faut recharger l’item courant en utilisant la même technique que pour le premier chargement :


//Refresh data when user is liking / unliking an item
function refreshLikeData(listId, itemId, like, itemtitle) {
   //get data from list item
   var clientContext = SP.ClientContext.get_current();
   var list = clientContext.get_web().get_lists().getById(listId);
   clientContext.load(list, 'Title', 'Id');
   var item = list.getItemById(itemId);
   clientContext.load(item, "LikedBy", "ID", "LikesCount", "Title");
   clientContext.executeQueryAsync(
         Function.createDelegate(this, function(){onRefreshLoadDataSucceeded(itemId, like, item, list);}),
         Function.createDelegate(this, onRefreshLoadDataFailed)
   );
}

Et enfin on injecte les mises à jour toujours via du JQuery (que j’aurai pu factoriser…) :



//when refresh data succeeds, change html controls like/unlike and tooltip, number of likes
function onRefreshLoadDataSucceeded(itemId, like, item, list) {
	
   //set number of likes
   $('.ms-comm-likesCount.ms-comm-reputationNumbers').text(item.get_item('LikesCount'));
	
   //change like button to unlike or unlike to like (or error) + command onclick
   if(like == "true") {
      $('a#likesElement-' + itemId).text('Unlike');
      $('a#likesElement-' + itemId).attr("onclick" , 'setLikeOnNews("' + list.get_id() + '","' + item.get_item('ID') + '","false","' + encodeURIComponent(item.get_item('Title')) + '");return false;');
   }
   else if(like == "false") {
      $('a#likesElement-' + itemId).text('Like');
      $('a#likesElement-' + itemId).attr("onclick" , 'setLikeOnNews("' + list.get_id() + '","' + item.get_item('ID') + '","true","' + encodeURIComponent(item.get_item('Title')) + '");return false;');
   }
   else {
      $('a#likesElement-' + itemId).text('ERROR');
      $('a#likesElement-' + itemId).attr("onclick" , '');
   }
	
   //change tooltip on like/unlike button
   var likers = [];
   var likerslist = '';
   var likerslimit = 5;
   var likerslimitOverpass = false;
   var currentUserId = _spPageContextInfo.userId;
   //Set likers on span
   if(item.get_item('LikesCount') != 0) {
      var likedBy = item.get_item('LikedBy');
		
      if (!SP.ScriptHelpers.isNullOrUndefined(likedBy)) {
         for (var i = 0; i < likedBy.length; i++) {
			
            likers[likers.length] = {
		id: String(likedBy[i].get_lookupId())
		, title: likedBy[i].get_lookupValue()
	    };
				
	    if (likers[i].id == currentUserId) {
               userAlreadyLike = true;
               likerslist = 'You \r' + likerslist;
            }
            else if(i < likerslimit) {
               likerslist += likers[i].title + '\r';					
            }
            else
               likerslimitOverpass = true;
            }
            if(likerslimitOverpass == true) {
               likerslist += '...\r';
            }	
         }
	else {
           likesCount = 0;
         }
      }
      else {
         likesCount = 0;
      }   	
	
   $('.ms-comm-likesMetadata.ms-metadata').attr('title',likerslist.replace(/\r$/, ""));
}

//Function called if refresh data on like/unlike controls fails
function onRefreshLoadDataFailed(sender, args) {
   //set number of likes
   $('.ms-comm-likesCount.ms-comm-reputationNumbers').text(item.get_item('ERROR'));
   $('a#likesElement-' + itemId).text('ERROR');
   $('a#likesElement-' + itemId).attr("onclick" , '');
}

Et tout devrait fonctionner ! La preuve en images… et en français :
15

16

Voici donc un exemple “assez simple” d’une personnalisation, sans code serveur, sans solution à déployer… et qui ouvre la porte à de nouvelles personnalisations.
Et je sais que certains vont me poser la question : “Mais ça y sera dans SP2016 ?” ===> La réponse est OUI 🙂

A bientôt !

SP2010-Remplacer les formulaires – Part 1 – Template de Liste

Posted on Updated on

Bonjour à tous.

Cela fait maintenant quelques semaines que je n’ai pas bloggé, je profite d’une petite “accalmie” pour publier un article qui vous montrera comment déployer des formulaires NewForm.aspx; EditForm.aspx et DispForm.aspx dans SharePoint 2010 (fonctionnera également avec SharePoint 2007 & 2013).

Bien souvent, quand je me déplace chez des clients ou que je reprend des développements qui ont été réalisés, je m’aperçois que ces formulaires ont été ré-implémentés et redéployés par le biais d’une solution SharePoint, ce qui est plutôt bien. Ce qui me choque en général, c’est que ces formulaires ne viennent pas remplacer “physiquement” les existants mais s’ajoutent aux formulaires existants, avec des nouveaux noms : newform2.aspx, editform2.aspx ou encore dispform2.aspx.

Si des utilisateurs (malins) connaissent les url vers les formulaires natifs, ils pourront toujours les utiliser !!! Et ça, c’est vraiment pas bien !

Je vais donc tenter de vous expliquer comment remplacer ces formulaires (les écraser en fait) par le biais d’une solutions SharePoint… et sans utiliser du C# => Chalenge Sourire

Nous essaierons d’utiliser au maximum les API natives de SharePoint et les fichiers XML de déploiements supportés.

Dans un second temps, j’aimerai également vous montrer comment remplacer/overrider le comportement natifs des contrôles dans ces formulaires… mais ça sera pour un prochain article !

Première chose, se connecter sur une collection de sites, un site et créer une liste. Dans mon cas ce sera une liste custom… et même une seconde liste que je créée par avance pour la suite de l’article. Je vais également créer une colonne de type lookup (recherche d’éléments déjà présents sur le site) qui “reliera” mes deux listes… Et encore une troisième qui utilisera elle aussi une colonne de type lookup :

On aura donc trois listes avec leurs attributs :

image

  • Pays contiendra le nom du pays, sa surface et son nombre d’habitants
  • Ville contiendra le nom de la ville, le pays d’appartenance, sa surface et son nombre d’habitants
  • Commande contiendra le titre de la commande, le montant, le pays et la ville. Dans un premier temps on utilisera deux lookup, pointant sur chacune de listes pays & ville. On remplacera plus tard ce comportement par des listes déroulantes liées et pré-filtrées. On sélectionnera le pays et cela filtrera la liste des villes proposées. Mais tout cela dans un second temps.

1/ Commençons par créer une solution avec Visual Studio 2010

NB : il faudra travailler un maximum en utilisant les noms internes des objets (List, Site, Web, Field) afin d’éviter les problème de renommage de ces objets (display name). On utilisera donc au maximum les staticName, internalName ou encore Name lorsqu’il seront disponibles sur ces éléments.

Commençons donc par la création de notre solution SharePoint dans Visual Studio 2010 que j’appelle DEMO1, de type “Empty SharePoint Project” :

image

On renseigne l’url du site de déploiement (debug) et on choisit une solution de type “Farm” :

image

La solution est créée, elle est vide ou presque :

image

Nous allons ensuite faire un clic droit sur le projet et ajouter un nouvel élément (Add New Item), de type SharePoint > 2010 > List Definition et on la nomme “Pays”. Cliquer sur “Add” :

image

Un assistant se lance pour nous aider à créer la structure générale de ce nouvel élément. Ainsi, on renseigne le nom d’affichage de notre modèle de liste. “Pays” dans mon cas, et on choisit le modèle “Custom List” qui est basique et conviendra très bien à notre exemple. Je choisis également de créer une instance lorsque la “Feature” déployant notre solution sera activée. On clique sur “Finish” :

image

La structure générale de la liste est créée :

image

On observe tout de suite que Visual Studio n’a pas créé 1 seul fichier mais bien un ensemble. Dans le “Répertoire” Pays, on retrouve 2 fichiers XML :

  1. Elements.xml : décrit à SharePoint comment déployer le modèle lors de l’ajout de ce modèle
  2. Schema.xml : décrit la structure des éléments à déployer : colonnes, vues, etc.

Dans le sous dossier ListInstance1, on retrouve également un fichier XML “Elements.xml” qui décrira comment créer une instance de cette liste à partir du modèle lors de l’activation de la Feature : nom de la liste, url, visibilité, etc.

Nous allons ensuite reproduire 2 fois ces manipulations pour créer deux nouvelles listes : Ville et Commandes :

image

J’ai pris le soin ici de renommer correctement les instances dans l’explorateur de solutions.

2/ Modifier les modèles de liste – Elements.xml

Notre structure générale est créée. Passons maintenant à l’enrichissement de ces modèles en les personnalisant, en ajoutant de nouvelle colonnes, etc.

Pays

Pour la liste pays, nous devons :

  • Ajouter les colonnes Surface, NbrHabitants
  • Masquer le modèle dans la galerie (les utilisateurs/power users ne pourront pas créer de nouvelles instances à partir de ce modèle)
  • personnaliser par-ci, par-là Sourire

Commençons par le fichier Elements.xml du modèle de liste :

<ListTemplate
Name=”Pays”
Type=”10000″
BaseType=”0″
OnQuickLaunch=”TRUE”
SecurityBits=”11″
Sequence=”410″
DisplayName=”Pays”
Description=”My List Definition”
Image=”/_layouts/images/itgen.png”/>

Nous allons commencer par modifier les attributs existants :

  • Name=”Pays” => cela me convient
  • Type=”10000” (attention il ne faudra pas avoir un autre modèle sur la plateforme portant ce numéro)
  • BaseType=”0” => cela me convient (on aura 1 pour les bibliothèques de documents)
  • OnQuickLaunch=”TRUE” => Je change en FALSE, la liste n’apparaitra pas pas défaut dans la barre de navigation rapide “QuickLaunch”
  • SecurityBits=”11″ => OK
  • Sequence => aucune importance, le modèle sera masque dans la galerie des modèles de liste (ordre de tri dans cette galerie)
  • DisplayName=”Pays” => OK
  • Description=”My List Definition” => On donne une description
  • Image=”/_layouts/images/itgen.png”/ => Je vais changer pour fournir une image un peu plus sympa, que je déploierais grâce à la solution dans 14/TEMPLATES/IMAGES/DEMO/monimage.png

On ajoutera les attributs :

  • Category=”Custom Lists” => pas obligatoire
  • DisableAttachments=”TRUE” => on désactive les pièces jointes
  • FolderCreation=”FALSE” => on désactive la création de répertoires
  • Hidden=”TRUE” => On masque le modèle dans la galerie
  • VersioningEnabled=”FALSE” => on désactive le versioning

Ce qui donne :

ListTemplate
Name=”Pays”
Type=”10000″
Category=”Custom Lists”
DisableAttachments=”TRUE”
FolderCreation=”FALSE”
Hidden=”TRUE”
VersioningEnabled=”FALSE”
BaseType=”0″
OnQuickLaunch=”FALSE”
SecurityBits=”11″
Sequence=”410″
DisplayName=”Pays”
Description=”Modèle de liste pays”
Image=”/_layouts/images/demo/pays.png”/>

Notez également qu’il vous sera possible de “localiser” les noms (Name, Description par exemple) pour faire la traduction automatique suivant la langue du navigateur/UI.

Ville

Nous allons effectuer les mêmes manipulation sur le fichier Elements.xml du modèle de liste Ville :

<ListTemplate
Name=”Ville”
Type=”10001″
Category=”Custom Lists”
DisableAttachments=”TRUE”
FolderCreation=”FALSE”
Hidden=”TRUE”
VersioningEnabled=”FALSE”
BaseType=”0″
OnQuickLaunch=”FALSE”
SecurityBits=”11″
Sequence=”410″
DisplayName=”Ville”
Description=”Modèle de liste ville.”
Image=”/_layouts/images/demo/villes.png”/>

Commandes

Pour la liste commande, on reproduit les mêmes manipulations, sauf que ne masquera pas ce modèle pour pouvoir créer de multiples instances de cette liste. Nous modifierons également l’attribut OnQuickLaunch à TRUE :

<ListTemplate
Name=”Commandes”
Type=”10002″
Category=”Custom Lists”
DisableAttachments=”TRUE”
FolderCreation=”FALSE”
VersioningEnabled=”FALSE”
BaseType=”0″
OnQuickLaunch=”TRUE”
SecurityBits=”11″
Sequence=”410″
DisplayName=”Commandes”
Description=”Modèle de liste commandes.”
Image=”/_layouts/images/demo/commandes.png”/>

3/ Modifier les modèles de liste – Schema.xml

Maintenant, il nous faut modifier les 3 fichiers Schema.xml pour ajouter les nouvelles colonnes sur chacun de nos 3 modèles.

Pays

Dans notre liste pays, nous allons ajouter deux colonnes supplémentaires : Surface & NbrHabitants. Pour cela, on ouvre le fichier Schema.xml du modèle de liste Pays :

image

Dans cette structure XML, on distinguera 4 parties principales :

  • ContentTypes : déclaration des types de contenus associés à la liste, ici celui élément (Item)
  • Fields : métadonnées/colonnes/champs associés à ce modèle de liste
  • Views : Vues disponibles sur ce modèle de liste
  • Forms : formulaires d’ajout/modification/affichage de chaque élément de cette liste

Nous allons modifier principalement les <Fields /> pour ajouter les deux colonnes. Ici plusieurs écoles s’affrontent. Vous pouvez partir de rien, aidé par l’IntelliSense de Visual Studio ou alors créer une liste et ces colonnes dans SharePoint et utiliser SharePoint Manager 2010 pour récupérer le schéma généré par SharePoint. Pour sa facilité, je choisirai la seconde option, mais en restant vigilant sur le flot de XML généré…

J’ai donc créé une liste “pays” dans SharePoint et créé les colonnes demandées.

On prendra soin également de choisir le type de champ le plus pertinent pour nos données : texte, monnaie, nombre, recherche, etc.

image

    Voila donc le formulaire d’ajout d’un pays :

image

Vous trouverez de quoi télécharger SharePoint Manager 2010 sur CodePlex : SharePoint Manager 2010.

On lance SharePoint Manager 2010, et on explore l’arborescence de nos sites jusqu’à arriver sur notre liste “Pays” :

image

Dans la section de droite, vous trouverez un onglet “SchemaXml” avec l’ensemble du schéma :

image

On repère rapidement, dans la section <Fields> que nos deux champs créés précédemment apparaissent :

  • <Field Type=”Number” DisplayName=”Surface” Required=”FALSE”… />
  • <Field Type=”Number” DisplayName=”NbrHabitants” Required=”FALSE” … />
    Ce sont ces deux nœuds XML qu’il va falloir intégrer au fichier Schema.xml. On copie donc ces deux lignes et on les colle dans Schema.xml, dans le nœud <Fields></Fields> :

image

Petite option supplémentaire, on peut directement ajouter ces deux champs dans les différentes vues proposées par notre modèle de liste Pays. Pour cela, dans Schema.xml, on ouvre le nœud <Views>, on y trouve deux nœuds <View></View>. Une des vues sera utilisée pour les terminaux de type mobiles, et l’autre pour l’affichage par défaut dans le navigateur de cette liste “Pays”. Nous allons directement modifier la vue commençant par :

<View BaseViewID=”1″ Type=”HTML” WebPartZoneID=”Main”

Et ajouter dans le sous-noeud <ViewFields> deux nouvelles lignes (en copiant/collant 1 existante) et en modifiant l’attribut Name avec les noms de noms champs. Je supprime également le Field Attachments correspondant aux pièces jointes que j’ai désactivé dans la déclaration du Template (voir Elements.xml précédent) :

        <ViewFields>
<FieldRef Name=”LinkTitle”></FieldRef>
<FieldRef Name=”Surface”></FieldRef>
<FieldRef Name=”NbrHabitants”></FieldRef>
</ViewFields>

Attention, l’ordre a son important, les premières lignes deviendront les premières colonnes dans l’affichage. On obtiendra donc :

image

Notez également le nœud <OrderBy> au dessous qui vous permettra d’ordonner votre liste sur les noms des pays, alphabétiquement. Je modifie donc :

        <Query>
<OrderBy>
<FieldRef Name=”Title”></FieldRef>
</OrderBy>
</Query>

C’est tout pour ce modèle de liste. Passons maintenant à l’instance qui sera créé à partir de ce modèle. Pour cela, on se rend dans l’explorateur de solution et on ouvre le fichier Elements.xml de l’instance de la liste Pays :

image

Dans ce dernier, nous allons encore modifier la structure XML pour paramétrer l’instance de la liste Pays qui sera automatiquement créée lors de l’activation de la feature (nous y reviendrons…).

On modifie pour obtenir :

<ListInstance Title=”Pays”
OnQuickLaunch=”FALSE”
TemplateType=”10000″
Url=”Lists/Pays”
Description=”Liste des pays”>
</ListInstance>

Notez que le TemplateType est le même que celui définit dans le <ListTemplate>, c’est très important ! Sinon SharePoint ne saura pas quel modèle utiliser pour créer l’instance. Pour l’url, ma liste sera accessible à l’adresse :

Ville

Nous allons reprendre les mêmes manipulations pour ce modèle de liste. Nous allons utiliser également SharePoint Manager 2010 pour récupérer les nœuds XML des colonnes.

Nous utilisons SharePoint Manager 2010, récupération des nœuds XML, copier/coller, etc.

image

Dans le Schema.xml, nous  ajoutons donc :

<Fields>
<Field Type=”Number” DisplayName=”Surface” Required=”FALSE”…

<Field Type=”Number” DisplayName=”NbrHabitants” Required=”FALSE”…

<Field Type=”Lookup” DisplayName=”Pays” Required=”TRUE”…

</Fields>

Jusqu’ici, nous n’avions eu que des colonnes texte ou nombre. Nous venons d’ajouter un nouveau type de colonne, le type “Lookup” (Recherche d’information déjà présentes sur le site). Ces champs permettent de référencer des données qui sont stockées dans une autre liste/bibliothèque SharePoint. Si l’on regarde les différents attributs proposés, on note :

  • List=”{1f675688-39f5-441d-bdb7-19f7c3dce646}”
  • ShowField=”Title”
    Ce sont ces deux attributs qui établissent la relation entre la liste Ville et la liste Pays et plus particulièrement la colonne “Title” de la liste Pays. Lorsque nous déploierons cette solution/feature, SharePoint ne saura pas faire le lien entre ces deux listes, parce que, tout simple le GUID de la liste Pays va être régénéré !!! Et oui… il va donc falloir remplacer ce premier attribut “List” par autre chose (et si possible pas de code C# qui recréera cette relation).
    Après un petit tour dans le SDK de SharePoint, on s’aperçoit qu’il est possible de remplacer ce GUID par l’adresse relative de cette liste : Lists/Pays. On obtient donc :

<Field Type=”Lookup” DisplayName=”Pays” Required=”TRUE” EnforceUniqueValues=”FALSE” List=”Lists/Pays” ShowField=”Title”

Pour la vue par défaut de cette liste, nous effectuons également les modification afin que ces champs apparaissent dans la vue :

image

C’est tout pour ce modèle de liste. Passons maintenant à l’instance qui sera créé à partir de ce modèle. Pour cela, on se rend dans l’explorateur de solution et on ouvre le fichier Elements.xml de l’instance de la liste Ville :

image

Dans ce dernier, nous allons encore modifier la structure XML pour paramétrer l’instance de la liste Ville qui sera automatiquement créée lors de l’activation de la feature (nous y reviendrons…).

On modifie pour obtenir :

<ListInstance Title=”Villes”
OnQuickLaunch=”FALSE”
TemplateType=”10001″
Url=”Lists/Villes”
Description=”Liste des villes.”>
</ListInstance>

image

Notez que le TemplateType est le même que celui définit dans le <ListTemplate>, c’est très important ! Sinon SharePoint ne saura pas quel modèle utiliser pour créer l’instance. Pour l’url, ma liste sera accessible à l’adresse :

Commandes

Pour cette dernière liste, nous allons reproduire exactement les mêmes étapes mais cette fois-ci, nous aurons deux champs de type “lookup”.

Dans le schema.xml nous aurons donc :

<Fields>
<Field Type=”Currency” DisplayName=”Montant” Required=”TRUE” … />
<Field Type=”Lookup” DisplayName=”Pays” Required=”TRUE” …  List=”Lists/Pays” ShowField=”Title” … />
<Field Type=”Lookup” DisplayName=”Ville” Required=”TRUE” List=”Lists/Villes” ShowField=”Title” … />

</Fields>

image

C’est tout pour ce modèle de liste. Passons maintenant à l’instance qui sera créé à partir de ce modèle. Pour cela, on se rend dans l’explorateur de solution et on ouvre le fichier Elements.xml de l’instance de la liste Commandes :

image

Dans ce dernier, nous allons encore modifier la structure XML pour paramétrer l’instance de la liste Commandes qui sera automatiquement créée lors de l’activation de la feature (nous y reviendrons…).

On modifie pour obtenir :

<ListInstance Title=”Commandes”
OnQuickLaunch=”TRUE”
TemplateType=”10002″
Url=”Lists/Commandes”
Description=”Liste de commandes.”>
</ListInstance>

image

Notez que le TemplateType est le même que celui définit dans le <ListTemplate>, c’est très important ! Sinon SharePoint ne saura pas quel modèle utiliser pour créer l’instance. Pour l’url, ma liste sera accessible à l’adresse :

Certains diront qu’il y a des attributs qui ne seront pas utilisé dans notre structure XML (Schema.xml, Elements.xml), je vous laisse vous reporter au SDK, Technet et autre pour faire le tri….

4/ Créer le package de déploiement

Il nous reste une étape, et pas des moindres, créer la solution, la feature qui permettra de déployer nos modèles de listes. Pour cela, Visual Studio nous aide bien (et c’est tout à son honneur quand on a connu SharePoint 2003 & 2007…).

Nous allons donc nous rendre dans le répertoire Features où nous remarquons qu’une feature est déjà disponible :

image

On double-clique, l’assistant de configuration des features apparait :

image

Aucun des modèles/instances n’est ajouté à la feature. Commençons par cela, on utilise les “flèches du milieu” pour faire passer les éléments de gauche à droite :

image

Nous allons également renommer la feature, donner une petite description, etc. Il vous sera également possible d’ajouter une petite icone pour distinguer plus rapidement vos features.

Chose importante, je souhaiterai que mes listes Pays & Villes ne soient créées (leurs instances plus particulièrement) que sur le site à la racine de la collection (RootWeb). Pour cela, le plus simple et de changer le Scope (Etendue) en Site (Collection de sites).

image

Et c’est tout… Bien sûr suivant le projet et les éléments qui le composent, vous choisirez peut être d’utiliser plusieurs fonctionnalités, avec des scopes différents…

5/ Le test !

C’est parti pour le test… vous pouvez utiliser F5 (mode debug… qui ne débuggera rien ici !) ou alors le clic droit sur le projet, Build, Deploy. Ce choisis la seconde option.

Vous aurez peut être une erreur lors du déploiement vous disant que Pays et Pays se déploie dans le même emplacement. En effet en regardant les chemins (path) de déploiement on s’aperçoit que les instances se déploient dans le même répertoire que la définition. Je renomme dans les modèles de liste :

image

Vous obtiendrez peut être cette fenêtre de warning :

image

Elle vous informe tout simplement qu’une liste avec le même nom “Pays” existe deja. On coche “Do not prompt me again for these items” puis on clique sur “Resolve automatically”…

Déploiement réussi :

image

Il faudra également ajouter les images correspondant aux miniatures de nos listes. J’ai fait quelques petits changements dans les Elements.xml des modèles en modifiant l’attribut Image :

  • Image=”/_layouts/images/demo/pays16x16.png”/
  • Image=”/_layouts/images/demo/villes16x16.png”/
  • Image=”/_layouts/images/demo/commandes16x16.png”/

Voila ce que cela donne dans le menu “View all site content” :

image

Rendons nous sur chacune des liste afin de vérifier :

  • La déclaration des vues/affichages (colonnes présentes, tris)
  • La déclaration des colonnes créées

Coté affichage, rien à signaler. Par contre si on lance le formulaire d’ajout d’un pays/ville/commandes, le formulaire ne présente que le champ Titre !!! Effectivement, c’est un comportement particulier de SharePoint voire étrange que l’on constate ici.

Premier réflexe, ajouter les attributs :

ShowInNewForm=”TRUE” ShowInEditForm=”TRUE” ShowInDisplayForm=”TRUE”

Sur chacun des champs. Mais cela n’aura aucun effet. Nous aurons alors deux choix :

  • Recourir à un type de contenu SharePoint
  • Supprimer les types de contenu

En général, je me tourne vers la première option. Mais dans notre cas, aucun intérêt de créer des types de contenu (ContentTypes) vu qu’on ne les utilisera qu’une seule fois (pour Pays & Ville) et qu’on ne réutilisera pas non plus la structure de Commandes (si ce n’est pour créer de nouvelles listes de commandes => et pas des sous-enfants).

Voila donc comment faire fonctionner tout cela. Première opération, supprimer les sections <ContentTypes>…</ContentTypes> ou tout du moins le contenu => <ContentTypes/> dans les 3 Schema.xml :

image

On sauvegarde le tout et on redéploie… et là, magique :

image

5/ Charger des données

Une petite dernière chose, avant de passer à la suite… Les listes étant créées, elles seront supprimées, recréées à chaque déploiement depuis Visual Studio. Pour ne pas perdre trop de temps à se recréer un jeu de test, nous pouvons remplir ces listes directement depuis leur définition. C’est ce qu’on appelle les RowData.

Nous allons commencer par ajouter des données dans Pays puis Ville. Cette méthode est aussi très pratique pour remplir automatiquement des listes de paramètres stockées dans SharePoint (ici notre liste de Pays ne change pas tous les jours…). Voila comment faire.

Dans chacun des fichiers Elements.xml des instances de nos listes, nous allons ajouter un bloc de XML :

<ListInstance Title=”Pays”
OnQuickLaunch=”FALSE”
TemplateType=”10000″
Url=”Lists/Pays”
Description=”Liste des pays”>
<Data>
<Rows>
<Row>
<Field Name=””></Field>
</Row>
</Rows>
</Data>
</ListInstance>

En résumé, les nœuds XML :

  • Data : ensemble des données à ajouter
  • Rows : collection des lignes (ensemble d’enregistrements)
  • Row : 1 ligne d’enregistrement
  • Field : champ à valoriser pour l’enregistrement courant.

Donc pour un pays, on aurait (données recueillies sur www.france.fr) :

    <Data>
<Rows>
<Row>
<Field Name=”Title”>France</Field>
<Field Name=”Surface”>543965</Field>
<Field Name=”NbrHabitants”>65350000</Field>
</Row>
</Rows>
</Data>

En on recréée une structure Row pour chacun des pays. J’en ajouterai une petite dizaine pour les tests.

image

On remarque tout de suite que les pays sont triés sur le nom (alphabétique) alors dans le Schema.xml :

        <Row>
<Field Name=”Title”>France</Field>
<Field Name=”Surface”>543965</Field>
<Field Name=”NbrHabitants”>65350000</Field>
</Row>
<Row>
<Field Name=”Title”>Espagne</Field>
<Field Name=”Surface”>505911</Field>
<Field Name=”NbrHabitants”>46754784</Field>
</Row>
<Row>
<Field Name=”Title”>Angleterre</Field>
<Field Name=”Surface”>130395</Field>
<Field Name=”NbrHabitants”>52234000</Field>
</Row>

Nous allons faire la même chose avec les Villes. Petite différence, le champ lookup qui pointe sur la liste des Pays… Et c’est donc un peu différent. En effet, les champs lookups doivent être renseigné avec ID;#Valeur :

         <Row>
<Field Name=”Title”>Marseille</Field>
<Field Name=”Surface”>240</Field>
<Field Name=”NbrHabitants”>850602</Field>
<Field Name=”Pays”>1;#France</Field>
</Row>
<Row>
<Field Name=”Title”>Madrid</Field>
<Field Name=”Surface”>608</Field>
<Field Name=”NbrHabitants”>3413271</Field>
<Field Name=”Pays”>2;#Espagne</Field>
</Row>

Attention, l’ID et celui dans SharePoint (et donc dans l’ordre d’insertion qui correspond à l’ordre dans l’Element.xml de la liste Pays !)

image

Et voilà ! Puisque les listes sont supprimées à chaque déploiement, nous n’auront pas besoin de remplacer les ID dans les fichiers Elements.xml.

Dernière vérification, la liste de commandes est-elle bien renseignée ?? Bien sûr que oui !

image

Par contre on voit tout de suite que Barcelone n’est pas en Allemagne… Clignement d'œil

Et donc… prochain article… ré-implémenter le comportement de ce formulaire… et faire communiquer nos deux liste déroulantes !

Allez… c’est le moment “j’me la pète”

Posted on Updated on