1

Is there a way to display a specific JSF page based on the request URL?

Let's say I have a JSF page "details.xhtml". The managed bean "detailsBean" has a list of objects where each object has its own ID. Now if a user requests the page "../details.xhtml?id=1", the list should be queried for an object with ID 1 and the resulting details page of this object should be displayed.

I already wrote a converter implementation class which can convert from object to ID and vice versa, but I don't know how to use it properly. Do I have to work through the JAX-RS specification for this to work or is there a more simple solution?

Arjan Tijms
  • 37,782
  • 12
  • 108
  • 140
nico1510
  • 606
  • 9
  • 29

2 Answers2

4

In JSF you can do this by using a so-called view parameter. You declare these in the metadata section of your Facelet:

<f:metadata>         
    <f:viewParam name="id" value="#{yourBean.yourObject}" label="id" 
        converter="yourObjectConverter" 
    />
</f:metadata>

This will grab the URL parameter id from the request URL. E.g. if you request the page this appears on with localhost:8080/mypage.jsf?id=1, then 1 will be handed to the yourObjectConverter and whatever this converter returns will be set in yourBean.yourObject.

Your backing bean will thus get the converted object. No need to pollute your backing bean over and over again with the same query code.

@ManagedBean
public class YourBean {

    private SomeObject someObject;

    public void setYourObject(SomeObject someObject) {
        this.someObject = someObject;
    }
}

If your backing bean is view scoped, you may want to use the OmniFaces variant of viewParam instead, since otherwise it will needlessly convert after each postback (if your converter does a DB query, you definitely don't want this).

Working full examples:

Further reading:

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
Arjan Tijms
  • 37,782
  • 12
  • 108
  • 140
  • thanks but I'm looking for a way to resolve the request solely based on the URL. Above code only works from within a jsf page, but I would like it to work e.g for links in an email, too. – nico1510 Nov 11 '12 at 16:59
  • The above method will resolve the request solely based on the URL. The code does not know where the link comes from. Whether it's in an email, on another page or directly typed into the address bar of the browser. – Arjan Tijms Nov 11 '12 at 17:26
  • oh ok...I didn't knew that. Now the only problem remaining is that I need to access an application scoped bean from within my Converter and there I get a NullPointerExecption. I tried @ManagedProperty(value="#{myBean}")... – nico1510 Nov 11 '12 at 18:21
  • 3
    Converters by themselves are not injection targets in JSF 2.1 (they will be in JSF 2.2). But there is a workaround. See the `user_edit` link I posted. I uses `#{userConvertor}`, which means the converter is also a managed bean (see http://code.google.com/p/javaee6-crud-example/source/browse/src/entities/UserConvertor.java). When used that way, injection does work. Alternatively you can also look up `#myBean` programmatically, but you can try the converter as managed bean trick first. – Arjan Tijms Nov 11 '12 at 20:13
-1

You can achieve this with plain JSF with the following steps

  1. Capture the ID in the request to determine what object is being queried for in your DetailsBean from the request parameter. There are many ways to achieve this, one of which is adding the following annotation to your managed bean (this is currently only permitted for a @RequestScoped bean, see why here).

       @ManagedProperty(value="#{param.id}")
       int requiredObjectId;
    

    The annotation above will capture the id parameter from the request and assign it to the requiredObjectId variable.

  2. Using the captured Id, setup your object in your bean in a @PostConstruct method

       @PostConstruct
       public void queryForObject(){
    
       //use the requiredObjectId variable to query and setup the object in the backing bean
    
       }
    

    The object retrieved should be assigned as an instance variable of your managed bean

  3. In your view, you could then reference the queried object that has been setup in the backing bean

      <h:panelGrid columns="2">
         <h:outputText value="Title"/>
         <h:outputText value="#{detailsBean.selectedObject.title}"/>
      </h:panelGrid>
    

If your bean is in a scope broader than the request scope, you'll need a combination of constructs to cleanly pull that request parameter before view rendering.

  1. Capture the request parameter within the JSF view itself using

    <f:metadata>
     <f:viewParam name="id" value="#{detailsBean.requiredObjectId}" required="true" requiredMessage="You must provide an Object Id"/>         
    </f:metadata>
    
        **OR**
    
  2. Due to the nature of JSF Lifecycle processing, doing the above alone may not make the value available for your use in time for object setup. You could use the following instead.

     <f:metadata>
        <f:event type="preRenderView" listener="#{detailsBean.setObjectId}" />         
    </f:metadata>
    

    What we've done here is specify a method (that captures the id) in the backing bean that must be executed before the view is rendered, ensuring that the id parameter is available as at the time you need it. Proceed with step 3, only if you're using <f:event/> above.

  3. In the backing bean, you now define the setObjectId method

      public void setObjectId(){
    
      Map<String,String> requestParams =      FacesContext.getExternalContext().getRequestParameterMap();
      requiredObjectId =  Integer.parseInt(requestParams.get("id"));
    
      }
    

Note that the above option is generally a work around/hack and not a clean solution as such

Community
  • 1
  • 1
kolossus
  • 20,559
  • 3
  • 52
  • 104
  • I'm facing problems on step 1. I put the nodeid param into my managed bean like you described but I get the error message that Property nodeid for managed bean "detailsBean" doesn't exist... – nico1510 Nov 11 '12 at 16:50
  • 1
    The code in your `setIbjectId` seems unnecessary. You grab the parameter 'id' from the request there programmatically, but this is what the `viewParam` is already doing. – Arjan Tijms Nov 11 '12 at 17:31
  • @ArjanTijms, `setObjectId` is necessary because `viewParam` will not set the value until the `update model` phase, by which time it may be too late to use the value. The method is there so the id is available early during view build – kolossus Nov 11 '12 at 17:39
  • @polofan, did you create the getters and setters for `nodeid`? – kolossus Nov 11 '12 at 17:41
  • 2
    @kolossus you are right that in some very rare occasions viewParam is too late, but I don't see anything in OP's problem that suggests this is the case here. Maybe nothing needs to be done (like in Arjan's linked example), and otherwise PreRenderView is in 99.9% of all cases early enough. – Mike Braun Nov 11 '12 at 18:52
  • I have solve my problem with your 2nd solution but with thanks ;) – nico1510 Nov 11 '12 at 20:02
  • @MikeBraun, you're right on that one. `preRenderView` makes `viewParam` unnecessary. – kolossus Nov 11 '12 at 23:11
  • 2
    @kolossus that's not entirely true. PreRenderView works perfectly with viewParam, IF you need to do something with the injected parameter. What *is* unnecessary is getting the URL parameter from the request map and converting it manually. The viewParam already does this, and in a cleaner and easier to re-use way. – Mike Braun Nov 12 '12 at 09:17
  • @MikeBraun, whatever `viewParam` gives will NOT be available during `@PostConstruct`(which in retrospect is where I could have put the processing for the `setObjectId`). In this case, I decided to remove `viewParam` because like you said, it's needless here **because** of what I've already done in the `preRenderView` – kolossus Nov 12 '12 at 09:19
  • 2
    @kolossus indeed, so instead of PostConstruct you use PreRenderView IF you need to do something after the injection. But if the view param already does the conversion and validation, there *may* not be anything to do. The stuff you are doing in preRenderView is a bit of a bad practice. If the parameter is missing, or can't be converted you have to manually test for this and issue messages. With a view param this is automatic AND you can easily reuse converters and validators between many pages. My advice: remove that code in preRenderView and keep the view param ;) – Mike Braun Nov 12 '12 at 09:34
  • @MikeBraun, can't yank it out: OP is referencing it in comment and is using it. Cleaner to present it as an alternative – kolossus Nov 12 '12 at 09:47
  • I know and you're right. It's just that I'm afraid OP is now using a "bad practice". If you read BalusC's article, he never recommends the manual method. But just marking it as "not optimal" would be helpful. Thanks! – Mike Braun Nov 12 '12 at 09:55