Limiter le nombre de réponses d’une enquête

Bien souvent lors de la mise en place d’un SharePoint il est nécessaire de faire de nombreux ajustements par rapport aux fonctionnalités de base. Il est alors possible de sortir l’artillerie lourde et de développer (ou redevelopper) les fonctionnalités manquantes. On peut également “bricoler” l’existant lorsque les modifications à apporter sont minimes et concernent principalement l’interface utilisateur.

Prenons l’exemple d’une enquête. Le nombre de réponses par utilisateur peut bien entendu être limité mais le nombre de réponses au total est en revanche illimité. Si on utilise ce type de liste pour gérer une liste d’inscriptions à un évènement dont le nombre de places est restreint il va donc falloir effectuer quelques modifications.

La méthode que je vais utiliser est assez simple et peut-être appliquée à de nombreux problèmes : un DataForm WebPart et un peu de Javascript.

Le principe

Ma liste enquête comporte 2 questions : “Serez-vous présent?” et “Combien de personnes vous accompagneront?”.

survey

Lors de la validation à l’aide de l’un des deux boutons Terminer il faut donc vérifier si le nombre maximal d’inscriptions est atteint ou non et afficher un message en conséquence.

Je vais donc commencer par ajouter un DataForm WebPart à la page d’ajout d’un élément NewForm.aspx.
Ce WebPart récupérera le nombre total d’inscriptions déjà enregistrées puis générera du JavaScript pour effectuer la validation.

DataForm WebPart

Je commence par ajouter un DFWP (DataForm WebPart) à la page NewForm.aspx de ma liste enquête à l’aide de SharePoint Designer. Je l’ajoute juste après le ListForm WebPart dans la zone de WebPart.

Note : Il faut absolument éviter de modifier le ListForm WebPart déjà présent qui permet d’afficher le formulaire d’ajout d’un nouvel élément. Ne supprimez d’ailleurs jamais ce WebPart d’une page NewForm ou EditForm. Si vous souhaitez le remplacer cachez-le (attribut IsVisible).

Pour ajouter le DFWP : menu Vue de données –> Insérer une vue de données. Ensuite dans le panneau “Bibliothèque de sources de données”, clic sur le nom de la liste enquête –> “Afficher les données”.
On séléctionne alors la colonne “Serez-vous présent?” (ou n’importe quelle autre colonne puisque de toutes façons nous allons supprimer l’affichage qui sera généré par SharePoint Designer) puis “Insérer les champs sélectionnés en tant que” –> “Affichage de plusieurs éléments”.

dfwp_original

Filtrons maintenant les éléments retournés. En effet nous n’avons pas besoin de prendre en compte ceux dont la case “Serez-vous présent?” n’est pas cochée. On ouvre les tâches du DFWP (petite flèche en haut à droite du WebPart lorsqu’il est sélectionné) puis “Filtrer”. On indique alors que la colonne “Serez-vous présent?” doit avoir la valeur “Oui”.

dfwp_filter

Toujours dans les tâches du DFWP j’ajoute un paramètre “Limit” qui me permettra de configurer rapidement le nombre maximal de personnes pouvant s’inscrire.

dfwp_parameters

Dans l’affichage Code il va maintenant falloir faire le ménage. En effet je n’ai pas besoin d’afficher les différents éléments, je recopie donc simplement la variable Rows dans le template principal et je supprime les autres templates.

Il ne me reste alors plus que ca:

Ayant recopié tout le WebPart on s’y perd un peu. La seule partie qui nous intéresse pour le moment c’est celle qui se trouve entre les balises <XSL></XSL> :

C’est déjà plus clair. On remarque que mon template ne fait absolument rien pour le moment. Je vais commencer par rajouter quelques variables :

La variable “inscrits” contient donc le nombre total d’enregistrements retournés. Sachant que j’ai défini un filtre auparavant on n’obtient le nombre total d’enregistrements dont la valeur de “Serez-vous présent?” est égale à “Oui” c’est à dire le nombre d’inscrits.

La variable “inscripts_plus” contient le nombre d’accompagnateurs au total grâce à la fonction sum. Vérifiez bien que le paramètre concorde avec le votre.

Enfin je stocke le nombre d’inscrits au total dans la variable “total”.

JavaScript

Maintenant que le DFWP est configuré et qu’il me retourne les valeurs dont j’ai besoin il ne me reste plus qu’à ajouter le code JavaScript permettant de faire la validation. Je l’ajoute dans le template XSL juste après les variables:

L’utilisation de <xsl:value-of/> me permet d’injecter la valeur des variables XSL dans le code JavaScript.
Je récupère les différents éléments à l’aide de la fin de leurs IDs ce qui me permet de copier ce Webpart d’une enquête à l’autre. En effet l’id des contrôles est construit sur l’ID du Webpart et sur d’autres éléments fixes (TextBox, BooleanField, diidIOSaveItem).

Au final l’utilisateur obtiendra un message d’erreur (alert) lorsque le nombre d’inscriptions est dépassé.

result

Modification du paramètre Limit

Comment modifier rapidement le paramètre permettant de limiter le nombre d’inscriptions? Soit vous ouvrez SharePoint Designer et vous modifier la valeur par défaut soit vous utilisez votre navigateur.

Pour cela affichez la page NewForm.aspx de votre enquête où se trouve le DFWP. Ajoutez alors à l’URL le paramètre “ToolpaneView” avec une valeur de 2. Ce qui donne http://[urldel’application]/[site]/[enquete]/NewForm.aspx?Source=[URLderetour]&ToolpaneView=2. C’est ce qu’on appelle le ToolpaneView Hack.

La page s’affiche alors en mode d’édition. Si votre DFWP se trouve bien dans la zone de Webpart (à vérifier dans le code) alors vous avez la possibilité de le modifier. Dans le panneau de modification utilisez l’éditeur de paramètres pour modifier la valeur de Limit. Vous devez enregistrer les modifications puis quitter la page et y revenir pour vérifier le résultat.

Conclusion

L’exemple que je donne ici est très simple et il manque certaines étapes de validation. On pourrait par exemple désactiver le formulaire lorsque le nombre d’inscriptions est atteint. Il serait également plus intuitif d’afficher le nombre de places restantes sur le formulaire. Tout cela es bien entendu possible en JavaScript.

Le but de cet exemple était donc de montrer que l’utilisation d’un DFWP peut aller bien au délà du simple affichage de données et qu’il se révèle très pratique dans de nombreux cas.

Custom Field Types / Colonnes personnelles – PART 2

Dans mon post Custom Field Types – Part 1nous avons vu comment créer une colonne personnalisée très simple. Voyons maintenant comment créer une colonne personnalisée de type recherche (Custom Lookup Field Type) capable de filtrer les éléments grâce à une requête CAML. Si vous n’avez jamais développé ce type d’élément alors consultez mon premier post.

Le contexte

Voici le problème qui m’a ammené à m’intéresser aux Custom Field Types. Imaginons une liste de tâches “Affaires” et une autre liste de tâches “Actions”. Les affaires correspondent à des demandes clients et sont susceptibles de nécessiter la création de plusieurs actions afin de répondre à la demande.
Le plus simple est donc d’ajouter dans la liste Actions une colonne Affaire de type recherche. Cependant lorsque les Affaires passent en état “Terminé” il est toujours possible de créer des actions qui y sont liées. Ainsi après une période d’utilisation assez longue le nombre d’affaires est bien trop important et la création d’une action devient très fastidieuse.

Comment créer une colonne de type recherche avec un filtre sur les éléments recherchés? La solution la plus efficace est de mettre en place un Custom Lookup Field Type.

Le projet

Voici la structure du projet créé à l’aide de STSDEV (utilisez le type Empty Solution with C# assembly):

structure_projet On retrouve tous les éléments que nous avons utilisé dans l’exemple précédent:
Une classe pour notre FieldType (FilteredLookup2Field.cs).
Une classe pour le BaseFieldControl qui servira au rendu de notre colonne (FilteredLookup2Control.cs).
Le fichier XML Fldtypes qui contient la définition de notre Field Type.
Un fichier contrôle utilisateur qui sera lié à notre BaseFieldControl (FilteredLookup2Control.ascx).

Note : dans votre projet vous ne devriez pas avoir de dossier FEATURES et IMAGES ni de classe FeatureReceiver. Il s’agit ici d’une erreur de ma part lors de la création du projet.

FLDTYPES

Je ne reviendrai pas précisement sur le contenu de ce fichier. Seul le contenu de l’élément ParentType diffère et prend la valeur “Lookup”.
Les éléments Field qui se trouvent dans PropertySchema définissent les propriétés de la colonne qui seront demandées lors de l’ajout / édition de la colonne. Dans notre exemple on définit une propriété permettant de configurer la colonne sur laquelle porte la recherche et une seconde pour indiquer la requête CAML à utiliser.

Voici le résultat :

proprietes

SPFieldLookup

Il vous ensuite créer une classe héritant de SPFieldLookup dont le code correspond au comportement de votre colonne. Dans notre exemple il s’agit de la classe FilteredLookup2Field.

Reprenez les différents éléments de la classe MyFirstCustomField du projet précédent. Nous allons overrider une méthode supplémentaire : OnAdded. Celle-ci sera appelée lors de l’ajout de notre colonne à une liste.

Ligne 3 : GetCustomProperty(“LookupList”) permet de récupérer la valeur saisie pour la propriété LookupList lors de l’ajout de la colonne.

Ligne 9 : étant donné qu’on hérite de SPFieldLookup il nous est possible de définir la valeur des propriétés LookupList, LookupWebId et LookupField. Cette étape est indispensable pour que votre colonne soit véritablement liée à la liste recherchée. Votre colonne bénéficiera alors de toutes les fonctionnalités d’une colonne de type recherche (liens vers les éléments de la liste recherchée, mise à jour de votre colonne lors de modifications sur les éléments de la liste recherchée, etc…).

OnAdded est appelée après l’ajout de votre colonne à la liste et non pendant (comme son nom l’indique d’ailleurs). Ainsi les modifications que vous apportez doivent être suivies de this.Update() pour les appliquer. De même si les propriétés de la colonne sont incorrectes on ne peut pas annuler l’ajout de la colonne, il faut la supprimer d’où l’utilisation de this.Delete().

Web User Control

Notre contrôle utilisateur devra contenir une DropDownList pour afficher les éléments de la liste recherchée. Il serait également intéressant de pouvoir activer ou non le filtre CAML. Nous allons donc ajouter une CheckBox pour cela.

 

 

N’oubliez pas d’inclure vos contrôles dans un SharePoint:RenderingTemplate et de définir un ID qui vous permettra d’accéder à ces contrôles depuis le code behind.

BaseFieldControl

Il ne nous reste plus qu’à implémenter le code-behind de notre contrôle. Dans notre exemple c’est la classe FilteredLookup2Control qui s’en charge. Celle-ci hérite de BaseFieldControl.

Ici nous devons prendre en compte de plusieurs problèmes:

  • Que se passe-t-il lorsque la liste recherchée est vide et que notre colonne est un champ obligatoire?
  • Que se passe-t-il lorsqu’on édite un élément et que l’élément recherché a été modifié et qu’il est désormais filtré par la requête CAML?
  • Vérifier les autorisations de l’utilisateur sur la liste recherchée et ses éléments.

Dans cet exemple nous prendrons en compte les deux premiers problèmes je vous laisse gérer le problème de sécurité 😉

Commeçons par définir le DefaultTemplateName qui nous permettra d’accéder aux contrôles:

Passons maintenant à l’initialisation des contrôles:

Vous remarquerez que nous stoppons l’exécution de la méthode lorsque la colonne est en mode d’affichage (ControlMode == SPControlMode.Display). En effet le rendu d’une colonne en mode affichage doit être défini dans le fichier XML fldtypes grâce à l’élément RenderPattern (cela n’est pas obligatoire mais c’est la méthode la plus courante). Nous n’allons donc ici traiter que de l’affichage de la colonne en mode d’ajout / édition.

Ici nous accédons aux contrôles à l’aide du TemplateContainer. Cela ne fonctionne que si vous avez correctement overrider la propriété DefaultTemplateName.

On initialise ensuite notre DropDownList à l’aide de la méthode DropDownListLookup_Init en précisant qu’il faut filtrer les éléments.

Voici la méthode DropDownListLookup_Init en question:

Ici on récupère la liste recherchée et la requête CAML à l’aide des Custom Properties.
On recherche alors les éléments de notre liste en appliquant ou non le filtre CAML en fonction du paramètre filter.
Si aucun élément n’est retourné (pas d’élément dans la liste recherchée ou bien tous filtrés) on ajoute un élément vide dans la DropDownList. Dans le cas contraire on charge les éléments dans notre liste déroulante. On pense à respecter le format ID;#Title pour la valeur des éléments. En effet c’est sous ce format que sont stockées les valeurs d’un champ de type recherche.

Nous allons maintenant ajouter une méthode permettant de sélectionner le bon élément de notre DropDownList en fonction de la valeur de notre colonne. Nous la nommerons DropDownListLookup_SelectIndex.

On commence par caster la valeur du champ en SPFieldLookupValue ce qui facilite ensuite la manipulation de cette valeur.
Si la DropDownList contient la valeur c’est qu’elle n’a pas été filtrée, dans ce cas on sélectionne tout simplement l’élément correspondant. En revanche si la valeur est introuvable c’est qu’elle a été filtrée. On ajoute donc cette valeur à notre DropDownList mais on désactive cette dernière.

Voyons comment obtenir et définir la valeur de notre contrôle à l’aide de la propriété Value:

Pour ce qui est de récupérer la valeur rien de plus simple. On récupère la SelectedValue de notre DropDownList que l’on cast en SPFieldLookupValue. Etant donné que le format des valeurs de la liste déroulante correspond à celui d’une valeur de type recherche cela ne pose aucun problème.

La mise à jour de la valeur de notre contrôle est tout aussi simple puisqu’en réalité nous l’avons déjà traitée dans la méthode DropDownListLookup_SelectIndex().

Gérons maintenant l’activation / désactivation du filtre à l’aide de la CheckBox. Vous aurez remarqué que nous avons abonné la méthode CheckBoxEnableFilter_CheckedChanged à l’event CheckedChanged de notre CheckBox.

On réinitialise la liste déroulante en appliquant le filtre ou non en fonction de l’état coché ou non de la checkbox.

Dernière étape : la validation. En effet si la colonne est un champ requis et que la liste recherchée est vide ou bien que tous les éléments sont filtrés, il faut empêcher la validation de notre contrôle. Dans ce cas ou notre colonne n’est pas un champ requis on laisse passer 🙂
Nous devons donc overrider la méthode Validate:

On vérifie si notre élément vide a été ajouté à la liste déroulante. Si c’est le cas et que la colonne est requise alors on indique que la validation a échoué et on précise le message d’erreur. Celui-ci sera automatiquement affiché à la suite de nos contrôles définis dans FilteredLookup2Control.ascx

Il ne vous reste alors plus qu’à déployer votre projet 🙂

Pourquoi je ne peux pas déboguer mon projet?

Vous aurez probablement besoin de debuger votre projet. Pour cela clic-droit sur le nom de votre projet dans l’explorateur de solutions de Visual Studio –> Onglet générer. Sélectionnez une configuration autre que DebugBuild et cochez la définition des constantes DEBUG et TRACE. Répétez l’opération pour les autres configurations (DebugRedeploy, DebugQuickCopy, etc…). Vous pouvez ensuite attacher Visual Studio au processus w3wp.

Démonstration

Voici une petite démonstration du projet une fois déployé:

 
Filtered lookup field