[JSF] Navigation dynamique

Navigation dynamique [JSF] - Java - Programmation

Marsh Posté le 11-03-2010 à 18:27:13    

Bonjour à tous,
 
Je développe actuellement une application J2EE hébergée sur JBoss AS 5.0, utilisant principalement Hibernate + EJB3 (pour le métier), JSF 1.2 + Richfaces 3.3 + Facelets pour la partie Web de l'application.
 
Je ne suis pas à ce jour un grand connaisseur de tous ces frameworks, mais je me suis beaucoup documenté à ce sujet.  
 
J'ai bien intégré le fonctionnement des EJB (Entity et Session) avec les JSF (via des Managed Bean).
 
Là où est-ce que je bloque, c'est pour ma partie navigation entre les pages.
 
Afin d'illustrer ma question, je vais définir un exemple fictif.
 
Supposons que mon application gère plusieurs objets métier :  

  • des projets
  • des tâches
  • des tickets
  • des membres


De façon simpliste, on admettra ces quelques règles de gestion :

  • (1) Un membre peut créer, modifier un projet.  
  • (2) Plusieurs membres peuvent gérer un même projet.  
  • (3) Des tâches peuvent être créées pour un projet en particulier.
  • (4) Des tickets peuvent être créés pour un projet en particulier (anomalies, demande d'évolution ou d'assistance par exemple).


Une fois les EJB (Entity et Session) implémentés, il est très simple d'implémenter les différentes pages suivantes :
 
Les formulaires pour l'ajout/modification :  

  • editProject.xhtml
  • editTask.xhtml
  • editIssue.xhtml
  • editMember.xhtml


Formulaire d'identification pour l'espace membre :

  • login.xhtml


Pages d'accueil :

  • homeMember.xhtml : page d'accueil de pour un membre identifié, quand il s'identifie manuellement
  • homeProject.xhtml  : page d'accueil d'un projet, qui propose des liens pour afficher la liste des tâches, la liste des tickets, pour créer des tâches etc...


Puis les pages de listing :

  • listProject.xhtml
  • listTasks.xhtml
  • listIssues.xhtml
  • listMembers.xhtml


Maintenant, prenons un ou deux workflows de l'application.
 
I - Workflow 1
 
Je ne suis pas "membre" de l'application et je veux enregistrer un projet dans l'application pour faciliter sa gestion.
 
Afin de créer le projet, nous avons besoin de plusieurs informations :

  • Le compte du membre qui sera propriétaire du projet
  • Des informations concernant le projet à créer


Afin de mieux comprendre l'intérêt de ma question, imaginez que la page d'accueil de l'application soit en fait une liste de liens avec toutes les procédures possibles. Cette liste sera disponible à tout le monde (membre identifié ou non).
 
J'arrive donc avec mon navigateur préféré sur la page d'accueil, je ne suis pas identifié. Je clique sur le lien "Créer un nouveau projet".
 
La procédure "Créer Projet" se met donc en route. L'application connait la liste des informations nécessaires pour pouvoir effectuer la création du projet.
 
(1) la page login.xhtml m'est affichée :  

  • Si je possède un compte, je m'identifie, puis je passe à l'étape (2)
  • Si je ne possède pas de compte, je clique sur le lien "S'inscrire", puis je passe à l'étape (1-a)


(1-a) Le formulaire editMember.xhtml m'est affiché, je le remplis, puis je retourne à l'étape (1)
 
(2) Le formulaire editProjet.xhtml m'est affiché je le remplis, mon nouveau projet est créé, je passe à l'étape (3)
 
(3) La page d'accueil du projet (homeProject.xhtml) est affichée
 
II - Workflow 2
 
Dans l'exemple I, je me suis créé un compte et j'ai maintenant un projet. Je viens d'arriver sur la page d'accueil de l'application et je voudrais créer une tâche pour mon projet.
 
C'est donc une nouvelle procédure qui s'appelle "Créer tâche", accessible via un lien présent sur la page d'accueil de l'application. Cette procédure aura donc besoin :

  • d'un membre (pour savoir qui a créé la tâche)
  • d'un projet (pour savoir à quel projet la tâche est liée)
  • des informations concernant la nouvelle tâche


Je ne suis pas identifié sur l'application.
 
(0) Je clique sur le lien "Créer une tâche" depuis la page d'accueil de l'application.
 
La partie concernant l'information du membre est identique à l'exemple I. A partir de l'étape suivante, on admettra donc que l'on est identifié.
 
(1) La liste de mes projets est affichée (listProjects.xhtml) :

  • Si mon projet existe déjà, je le sélectionne, et je passe directement à l'étape (2)
  • Si mon projet n'existe pas encore, clique sur le lien "Nouveau projet", puis je passe à l'étape (1-a)


(1-a) la page editProjet.xhtml est affichée, je remplis le formulaire puis je valide
 
(2) J'arrive directement sur la page editTask.xhtml, qui me permettra d'ajouter une tâche pour le projet précédemment sélectionné. Je remplis le formulaire, puis valide, ma tâche est créée, fin de la procédure.
 
 
Ces deux exemples m'amènent cette question : est-il possible, et par quel moyen, de définir de tels workflows, en utilisant à chaque fois des pages de formulaires communes à plusieurs workflows ? Dans l'application, on pourrait accèder à chaque page unitairement en cliquant sur les liens proposés à chaque étape. Mais est-il possible de mettre en place des procédures qui enchaîneront les pages dans un ordre bien précis ?
 
J'ai regardé le système de navigation de base proposé par JSF 1.2, je n'ai pas trouvé de solution dans celui-ci. J'ai ensuite regardé ce que proposait JSF 2, et apparemment, les choses ont un peu changé, j'ai cru comprendre que les méthodes d'action des Managed Beans pouvaient retourner directement un view-id. La solution serait donc de créer un managed bean par procédure (workflow) et que les pages soient appelées dans l'ordre voulu grâce au retour des méthodes d'actions :
 
Avec pour exemple ceci : un managed bean MaProcédure1 qui possède les différentes action-methodes suivantes : doAction1(), doAction2(), doAction3() et la méthode 1 retournerait le view-id de la page pour l'action2, etc.
 
 Le truc qui gênent dans cette solution, c'est que :

  • JSF 2 avec JBoss est implémenté à partir de la version 6.0.0, et que la version actuelle n'est qu'une M2 (à en déduire BETA)
  • un formulaire pouvant être lié à plusieurs procédures, que faut-il mettre dans les pages xhtml pour définir l'action a exécuter (quel managed bean et quelle méthode), puisque suivant la procédure, ça change forcément.


Ca fait un très long TOPIC, merci d'avoir lu jusqu'ici, mais je voulais être le plus clair possible pour poser ma question.
 

Reply

Marsh Posté le 11-03-2010 à 18:27:13   

Reply

Marsh Posté le 15-03-2010 à 13:17:05    

Re-bonjour,
 
Ceci n'est pas un "up !", mais une réponse à moi-même.
 
J'ai trouvé une solution qui répondait à ma demande, et au cas où quelqu'un aurait les mêmes besoins que moi, au moins, il aura une solution possible.
 
Premièrement, je suis passé en JSF 2 (en prenant la version beta 2 de Jboss). Je me suis dit que pour un développement, une version beta pourrait suffire et comme l'application est assez conséquente à développer, il existera sans doute une version release au moment de mettre en production.
 
Donc, pour la maquette que j'ai faite, j'ai fait quelque chose de très simple : j'ai fait une procédure qui s'appelle "Carte d'identité", qui en fait, demande à l'utilisateur de saisir des informations sur lui (nom, prénom, date et lieu de naissance) puis de simplement afficher les informations saisies à la fin de la procédure. La saisie des informations a été faite en deux fois :

  • étape 1 : saisie du nom et du prénom depuis le formulaire /forms/name.xhtml
  • étape 2 : saisie de la date et lieu de naissance depuis le formulaire /forms/birth.xhtml


Dans cette maquette, j'ai deux types de managed beans :

  • ceux qui héritent de la classe abstraite FormBean (un managed bean par formulaire)
  • ceux qui implémentent l'interface FormBeanListener (un managed bean par procédure)


Les FromBean :
Ces beans ont donc pour seule fonction de gérer une page avec un formulaire. Par exemple, le formulaire name.xhtml permet de saisir un nom et un prénom, grâce à deux zones de texte. Le formulaire possède un bouton valider, et un bouton annuler.
 
La classe abstraite FormBean force l'implémentation des méthodes public void submit() et public void cancel(). La méthode public void setListener(FormBean Listener listener) qui permet d'ajouter un "écouteur" sur le bean.
 
Les boutons valider et annuler appellent respectivement les méthodes submit() et cancel() du managed bean associé. Lorsqu'une de ces deux méthodes sont appelées, les écouteurs sont mis au courant.
 
Voici le code de la méthode public void submit() d'un managed bean de formulaire. A savoir que la méthode cancel() sera écrite sur le même principe.
 

Code :
  1. public String submit()
  2. {
  3.   FormEvent e = new FormEvent(this, NameFormBean.getUrl()); // ici on défini l'url du formulaire en cours comme "prochaine" url par défaut. si elle n'est pas redéfinie, alors une fois le formulaire validé, on reviendra sur la meme page.
  4.   this.onSubmit(e); // on envoit l'event à l'écouteur éventuel, qui pourra redéfinir l'url contenue dans l'event
  5.   return e.getNextUrl();
  6. }


 
Les FormBeanListener :
Cette interface demande à implémenter les méthodes onSubmit(FormEvent event) et onCancel(FormEvent event). Vous l'aurez sans doute deviné, ces méthodes seront appelées lorsque le visiteur aura cliqué sur valider ou annuler sur le formulaire écouté.
 
la classe FormEvent représente un événement de formulaire (submit ou cancel), qui contiendra les informations suivantes :

  • l'instance du managed bean qui envoit l'événement
  • l'url qui va être atteinte suite à cette étape. cette propriété pourra être redéfinie par le listener, ce qui va nous permettre de pouvoir coder l'enchaînement des formulaires via notre bean de procédure.


Un bean décrivant une procédure aura comme propriétés :

  • tous les champs dont il aura besoin (dans le cas de la carte d'identité, firstName, lastName, birthDate et birthCity)
  • toutes les instances des managed bean FormBean dont il aura besoin (ici NameFormBean et BirthFormBean). Ces instances seront "remplies" par JSF via l'injection de dépendance.


Pour démarrer une procédure, on appellera la méthode public String start() de notre bean IdentityProcBean, qui va s'ajouter en tant que listener sur le premier formulaire (name.xhtml), puis retourner l'url du premier formulaire. Le code sera donc :

Code :
  1. public String start()
  2. {
  3.     this.nameForm.setListener(this);
  4.     return NameFormBean.getUrl();
  5. }

.
 
Une fois le formulaire validé, la méthode onSubmit(FormEvent event) sera appellée. Le bean devra donc récupérer et stocker les données saisies, puis, devra demander l'affichage du deuxième formulaire. Le code de cette méthode sera donc :

Code :
  1. public void onSubmit(FormEvent event)
  2. {
  3.   if(event.getForm().getClass() == NameFormBean.class)
  4.   {
  5.     NameFormBean f = (NameFormBean)event.getForm();
  6.     this.firstName = f.getFirstName();
  7.     this.lastName = f.getLastName();
  8.     f.setListener(null); // on a plus besoin d'écouter ce formulaire
  9.     this.birthForm.setListener(this);
  10.     event.setNextUrl(BirthFormBean.getUrl());
  11.   }
  12.   else if(event.getForm().getClass() == BirthFormBean.class)
  13.   {
  14.     BirthFormBean f = (BirthFormBean)event.getForm();
  15.     this.birthDate = f.getBirthDate();
  16.     this.birthCity = f.getBirthCity();
  17.     f.setListener(null); // on a plus besoin d'écouter ce formulaire
  18.     event.setNextUrl("/afficher.identite.xhtml" );
  19.   }
  20. }


 
Sur la méthode onCancel(FormEvent event), on peut par exemple écrire le code suivant, pour afficher un message comme quoi la procédure a été annulée :

Code :
  1. public void onCancel(FormEvent event) { event.setNextUrl("/canceled.xhtml" ); }

.
 
Avec ceci donc, ça fonctionne très bien. Cela requiert JSF 2 pour avoir la possibilité de retourner directement l'URL dans des méthodes de beans. Le fichier web.xml ne sert donc plus du tout pour la navigation et chaque procédure peut donc utiliser, dans l'ordre qu'elle le désire, chaque formulaire.
 
Je suis à vous si vous avez des questions, remarques :)

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed