sjfern
sjfern

Reputation: 83

How can I create a reusable view using JSF

Here is the scenario - I want to create a view which can be used from a number of other views to create and edit a specific type of object.

My application has an address entity which can be shared between other entities. In the view which maintains an entity, I would like a button/link which navigates to the address edit view for the address linked to that entity. Another view which handles a different entity also needs to be able to navigate to the address edit view with its address. The address view would then navigate back to the calling view once editing is completed.

My problem is that I can't seem to find a way to pass the address entity from the first view into the address view.

I think I want some kind of conversation scope, but don't know how to get the address without knowing about the page bean that references it, but obviously my address view can only know about addresses.

I am using JSF2.1 (MyFaces/PrimeFaces) and CDI (OpenWebBeans) and CODI.

I'm sure I must be missing something simple. (Simple relative to JSF/CDI terms that is!)

Upvotes: 4

Views: 807

Answers (3)

Gauthier Peel
Gauthier Peel

Reputation: 1518

If you want another entity to be set when the address is OK, just give the adresss process the EL name of the bean you want to set :

<f:param name="targetBeanSetter" value="enquiryBean.adress" />

And in Java :

public String executeAndBack() {
    int last = this.targetBeanSetter.lastIndexOf('.');
    String base = this.targetBeanSetter.substring(0, last);
    String property = this.targetBeanSetter.substring(last + 1);
    Object yourEntityToSet = FacesContext.getCurrentInstance().getELContext().getELResolver().getValue(context.getELContext(), null, base);
    try {
            PropertyUtils.setSimpleProperty(yourEntityToSet, property, constructeurHolder.getConstructeur());
        } catch (Throwable e) {
            throw new RuntimeException(e.getMessage());
        }
    return from + "?faces-redirect=true";
}

If you only need access to the choosen Adress, without building linked objects, when you are back on the first page, just inject in EnquiryView using

@ManagedProperty(value="{address}")
Address adress;

Upvotes: 0

sjfern
sjfern

Reputation: 83

I think I have come up with a solution.

As I am using CODI I can leverage the ConversationGroup annotation. I've created an emtpy interface AddressConversation, then added this to all the backing beans that need to show the address/addressEdit.xhtml view, as well as the backing bean for the addressEdit view.

I'm also using CODI view config so my action methods return ViewConfig derived class objects.

@Named
@ConversationScoped
@ConversationGroup(AddressConversation.class)
public class AddressView implements Serializable
{
    private Class<? extends Views> fromView;
    private Address editAddress;
    private Address returnAddress;

    // Getters/setters etc...

    public Class<? extends Views> cancelEdit()
    {
        returnAddress = null;

        return fromView;
    }
}

So in a calling view I have (PrimeFaces commandLink)

<p:commandLink value="#{enquiryView.addressLinkText}" action="#{enquiryView.editAddress()}" immediate="true"/>

and in the backing bean EnquiryView I can @Inject an instance of AddressView in the correct conversation group, then set the address and return view properties when action method is called.

@Named
@ConversationScoped
@ConversationGroup(AddressConversation.class)
public class EnquiryView implements Serializable
{
    @Inject @ConversationGroup(AddressConversation.class) private AddressView addrView;

    public Class<? extends Views> editAddress()
    {
        addrView.setAddress(editEnq.getAddress());
        addrView.setFromView(Views.Enquiry.EnquiryEdit.class);
        return Views.Address.AddressEdit.class;
    }
}

I can also observe the navigation in EnquiryView and update the enquiry entity when an address has been "saved" in the address edit view.

protected void onViewConfigNav(@Observes PreViewConfigNavigateEvent navigateEvent)
{
    if (navigateEvent.getFromView() == Views.Address.AddressEdit.class && 
            navigateEvent.getToView() == Views.Enquiry.EnquiryEdit.class)
    {
        onEditAddressReturn();
    }
}

private void onEditAddressReturn()
{
    if (addrView.getReturnAddress() != null) {
        // Save pressed
        editEnq.setAddress(addrView.getReturnAddress());
    }
}

Upvotes: 2

BalusC
BalusC

Reputation: 1108632

Just pass the address ID as request parameter and have the target view to convert, validate and set it in the bean by <f:viewParam>.

E.g.

<h:link value="Edit address" outcome="addresses/edit">
    <f:param name="id" value="#{address.id}" />
</h:link>

and then in addresses/edit.xhtml

<f:metadata>
    <f:viewParam id="id" name="id" value="#{editAddressBacking.address}"
        converter="#{addressConverter}" converterMessage="Bad request. Unknown address."
        required="true" requiredMessage="Bad request. Please use a link from within the system." />
</f:metadata>
<h:message for="id" />

<h:form rendered="#{not empty editAddressBacking.address}">
    <h:inputText value="#{editAddressBacking.address.street}" />
    ...
</h:form>

In order to navigate back to the original page, you could pass another request parameter.

<h:link value="Edit address" outcome="addresses/edit">
    <f:param name="id" value="#{address.id}" />
    <f:param name="from" value="#{view.viewId}" />
</h:link>

(where #{view} is the implicit object referring to the current UIViewRoot)

and set it by <f:viewParam> as well so that you can in the submit method of the edit address backing bean just return to it:

public String save() {
    // ...

    return from + "?faces-redirect=true";
}

See also:

Upvotes: 2

Related Questions