2010-10-04 32 views
0

J'ai une application JSF contenant deux pages JSP qui affichent certaines des mêmes données à partir d'un objet conteneur de portée session. Chaque page affiche les données différemment, chacune dans une table de données qui varie entre les pages. Tout cela fonctionne correctement jusqu'à présent. Mon problème est que j'ai triché un peu avec la façon dont je devine quelle page a été demandée à l'intérieur de mes méthodes d'action de haricot de support. Sur chaque page, j'ai utilisé une liaison pour ma table de données.JSF - Comment déterminer la page JSP actuelle à partir de la méthode d'action du bean backing

draftReport.jsp:

<t:dataTable 
    border="1" 
    id="reportDraftDataTable" 
    binding="#{controller.reportDraftDataTable}" 
    value="#{sessionData.reportDraftAdapterList}" 
    var="currentRow" 
    rowClasses="dataTableOddRow, dataTableEvenRow"> 

report.jsp:

<t:dataTable 
    border="1" 
    id="reportDataTable" 
    binding="#{controller.reportDataTable}" 
    value="#{sessionData.reportAdapterList}" 
    var="currentRow" 
    rowClasses="dataTableOddRow, dataTableEvenRow"> 

I ont un grain support demande de portée (nommé Controller) avec certaines des méthodes d'action pour ces pages Plutôt que de dupliquer du code sur le bean backing (une méthode similaire pour chaque page JSP similaire), je voulais savoir quelle page était rendue et l'utiliser comme paramètre pour une méthode de gestionnaire générique (capable de gérer les actions des deux pages) le haricot backing. Donc, j'ai triché et fait ceci:

public class Controller { 
    ... 

    private HtmlDataTable preArrivalReportDataTable; 
    private HtmlDataTable preArrivalReportDraftDataTable; 
    private static enum ReportType { 
     NON_DRAFT, 
     DRAFT 
    } 
    ... 

    private ReportType determineReportTypeFromControlBindings() { 
     Validate.isTrue(this.preArrivalReportDataTable != null^
       this.preArrivalReportDraftDataTable != null, 
      "Either preArrivalReportDataTable XOR " + 
      "preArrivalReportDraftDataTable must be null in " + 
      "determineReportTypeFromControlBindings()"); 
     if (this.preArrivalReportDataTable != null) { 
      return ReportType.NON_DRAFT; 
     } else { 
      return ReportType.DRAFT; 
     } 
    } 
    ... 

    public String actionOnReport() { 
     ReportType reportType = null; 
     reportType = determineReportTypeFromControlBindings(); 
     handleReportAction(reportType); 
     return "REFRESH"; 
    } 
    ... 
} 

Cela a fonctionné OK méthodes d'action à l'intérieur de ma classe de contrôleur, mais je besoin d'ajouter une autre méthode qui a finalement brisé mon code aki:

public String getStyleClass() { 
     ReportType reportType = determineReportTypeFromControlBindings(); 
     switch (reportType) { 
      case NON_DRAFT: 
       return "styleA"; 
      case DRAFT: 
       return "styleB"; 
      default: 
       return null; 
     } 
    } 

Dans mon JSP , l'expression JSF-EL est située au-dessus de la liaison de contrôle pour la table de données que j'utilise dans le bean de support pour déterminer à quelle page je suis. À ce stade, le determineReportTypeFromControlBindings() lève une exception lors de la vérification de validation, vraisemblablement parce que la liaison de contrôle n'a pas encore eu lieu.

Je ne suis pas surpris que cela se produise. Il a toujours eu l'impression d'être dans le mauvais sens. Mais ma question est:

Quelle est la bonne façon de déterminer la page JSP actuellement demandée à partir d'une méthode d'action de haricot de support à la demande?

Dans le cas où cela est pertinent, j'utilise la bibliothèque de balises MyFaces 1.2 Tomahawk.

+0

Vous ne pouvez pas créer un contrôleur approprié, séparé du bean View/Data que vous transmettez à JSF pour le rendu? Une conception claire de MVC serait presque certainement meilleure que le piratage de contrôleur sur votre modèle. –

Répondre

1

Je peux penser à quelques approches, l'une est proactive, où la page indique au bean ce qu'est la vue, une réactive, où le bean infère la vue. Et le dernier serait d'avoir un haricot abstrait avec des implémentations concrètes pour le brouillon et le non-brouillon.

Je préfère la dernière approche, elle se sent le plus semblable à Java, et le moins hacky. Mais voici quelques idées de base sur la façon de faire les deux premiers.

Proactive: Appelez une méthode pendant la phase renderResponse pour définir le type de rapport. Je l'ai seulement fait dans les haricots tronqués de session, je ne sais pas comment cela fonctionnera dans la portée de la demande, vous devrez peut-être vérifier d'autres phases, ou éventuellement l'appliquer indépendamment de la phase réelle.

Controller.java

public void draftInitializer(PhaseEvent event) { 
    if (event.getPhaseId().equals(PhaseId.RENDER_RESPONSE)) { 
    reportType = DRAFT; 
    } 
} 

draftReport.jsp

<f:view beforePhase="#{controller.draftInitializer}"> 

réactive: Obtenir l'URL de la demande.

Controller.java

private String getRequestURL(){ 
    HttpServletRequest request = (HttpServletRequest)FacesContext.getExternalContext().getRequest(); 
    return request.getRequestURL(); 
    } 

    private boolean isDraft() { 
    return getRequestURL().contains(DRAFT_URL_IDENTIFIER); 
    } 
+0

+1 pour vos suggestions. J'ai décidé d'aller dans une autre direction qui est similaire à votre approche "réactive", mais utilise le UIViewRoot à la place de l'URL de la requête. –

0

Je fini par résoudre ce en examinant l'objet UIViewRoot qui est obtenu à partir du FacesContext lors de la demande. J'ai remplacé mon ReportType enum par un RequestedPage enum parce qu'il me semblait plus lisible.

public static enum RequestedPage { 
    REPORT, 
    REPORT_DRAFT, 
    UNKNOWN 
} 

Ensuite, j'ai créé une des constantes de chaîne de couple et une nouvelle méthode dans ma classe Controller.

private final static String REPORT_DRAFT_JSP_NAME = "draftReport.jsp"; 
private final static String REPORT_JSP_NAME = "report.jsp"; 

/** 
* This method should only be invoked from inside an action method. 
* An exception will be thrown if the method is called when either 
* the FacesContext or UIViewRoot are not available. This is normally 
* the case outside of an active request or before the RESTORE_VIEW 
* phase has been completed. 
* 
* @return A non-null RequestedPage reference 
*/ 
private RequestedPage determineRequestedPageFromViewId() { 
    FacesContext facesContext = FacesContext.getCurrentInstance(); 
    Validate.notNull(facesContext); 
    UIViewRoot uiViewRoot = facesContext.getViewRoot(); 
    Validate.notNull(uiViewRoot); 
    String viewId = uiViewRoot.getViewId(); 
    logger.info("view id: " + viewId); 
    RequestedPage requestedPage = null; 
    if (viewId.contains(REPORT_DRAFT_JSP_NAME)) { 
     requestedPage = RequestedPage.REPORT_DRAFT; 
    } else if (viewId.contains(REPORT_JSP_NAME)) { 
     requestedPage = RequestedPage.REPORT; 
    } else { 
     requestedPage = RequestedPage.UNKNOWN; 
    } 
    return requestedPage; 
} 

Le UNKNOWN ENUM est destiné à couvrir toutes les pages dont l'identité que je ne me soucie pas dans mes méthodes d'action. Tant que j'obéis aux contraintes que je mentionne dans ma méthode Javadoc, cela semble fonctionner correctement. La seule chose décevante à propos de cette approche est que je voulais faire la résolution RequestedPage dans la méthode initializer pour ma classe Controller. Malheureusement, cela ne fonctionnera pas parce que l'initialiseur est appelé avant que la phase RESTORE_VIEW ne commence et par conséquent UIViewRoot est toujours null.

Voici le code qui PAS travail:

@PostConstruct 
public void init() { 
    logger.info("init() has been invoked"); 
    RequestedPage requestedPage = 
     determineRequestedPageFromViewId(); 
    // An exception is always thrown before I get here... 
    this.theRequestedPage = requestedPage; 
    logger.info("init() finished"); 
} 

Je peux vivre avec cela, à moins que quelqu'un d'autre a une alternative simple à mon approche.