AAron
AAron

Reputation: 438

JSF 2.2: Passing bean attribute as ValueExpression to composite

I want to pass a non-managed (non-String) object as an attribute on a dynamically added composite component, and have it survive the session.

The JSF 2.2 ViewDeclarationLanguage#createComponent handles dynamic non-string attribute values to composite components differently than the older Mojarra dependent code (Application#createComponent). I can't find the approach that works completely with the JSF 2.2 technique, but it's probably me.

[I'm trying to remove Mojarra dependencies by converting to MyFaces (and also working around some other Mojarra issues). I'm using JSF 2.2, CDI Weld 2.2.16, Tomcat v8.0]

I'm instantiating different composite components like these programmatically (notice the bean attribute):

<cc:interface>
    <cc:attribute name="bean" required="true" type="com.aadhoc.cvc.spikes.extensionsapproach.ExtensionBeanInterface"/>
</cc:interface>

<cc:implementation>
    <h:panelGrid columns="2">
        <h:outputText value="Title:"/>
        <h:inputText value="#{cc.attrs.bean.title}"/>
    </h:panelGrid>
</cc:implementation>

In the older Mojarra dependent approach, I instantiate the non-managed bean object, and add it directly to the composite component as an attribute and it works great (I'm using @BalusC's great but Mojarra dependent sample code from OmniFaces Component#includeCompositeComponent):

ExtensionBeanInterface bean = Class.forName(className).newInstance();
attributes = new HashMap<String, Object>();
attributes.put("bean", bean); // Using bean object itself

[..]
UIComponent composite = application.createComponent(context, resource);
composite.getAttributes().putAll(attributes);
[..]

In JSF 2.2, I've found that I must pass a String ValueExpression instead of my bean object directly. I'm currently using this technique, and can't get it quite right:

FacesContext context = FacesContext.getCurrentInstance();
ELContext elContext = context.getELContext();

ValueExpression beanValExp = context.getApplication().getExpressionFactory()
            .createValueExpression(elContext, "#{customBeanVE}", ExtensionBeanInterface.class);
beanValExp.setValue(elContext, bean);
String beanValExpStr = beanValExp.getExpressionString();

attributes = new HashMap<String, Object>();
attributes.put("bean", beanValExpStr); // Using VE instead of bean object

UIComponent composite = context.getApplication().getViewHandler()
                .getViewDeclarationLanguage(context, context.getViewRoot().getViewId())
                .createComponent(context, taglibURI, tagName, attributes);
[..]

This works great on the first "add composite", but on any following form submit, I get:

/resources/com/aadhoc/cvc/spikes/extensionsapproach/components/House.xhtml @16,49 value="#{cc.attrs.bean.title}": Target Unreachable, 'bean' returned null

I've verified that the composite's required and type attributes are working fine, and that the #{cc.attrs.bean.title} is initially showing the bean's title. I verified with a static use of the composite component that refreshes work fine.

What's the deal, and how can I handoff the bean object so that it survives with the composite across the session?

Upvotes: 1

Views: 1291

Answers (1)

AAron
AAron

Reputation: 438

I had this working great in Mojarra. I could put the bean object in the attribute value, and all was wonderful. Trying MyFaces, I needed to change/update my approach, and I now needed to use EL strings instead of direct object references.

Since all was working with just putting bean object into attributes Map, I wanted a magical yet elegant place to put bean objects and have them survive. I could have put them into the "global" session Map (like this: How to save an object into JSF Session), but it wasn't clean enough. I then put them into my one main session state bean (modelerBean), and it was right. This is how I saved the bean, and how I pointed to it with an EL string. No need to create ValueExpression or MethodExpression objects or register special mappings. This JSF 2.2 compatible approach worked for me in both Mojarra and MyFaces.

public void onAdd(ActionEvent ev) {
    [..]
    ChosenBean chosenBean = new ChosenBean();
    chosenBean.setComponentId(id);
    chosenBean.setBean(bean);
    modelerBean.addChosen(chosenBean);
    [..]
    String el = "#{modelerBean.getChosen('"+id+"').bean}"
    attributes.put(MODELER_EXTENSION_BEAN_ATTRIBUTE_NAME, el);
    [..]

I decided on this after reading @BalusC's post: How do I get and set an object in session scope in JSF?

Note, my experience with @BalusC's two "add composites dynamically" approaches (How to programmatically or dynamically create a composite component in JSF 2) is that you should definitely use the JSF 2.2 approach if you can. The old Mojarra approach does work if you aren't in JSF 2.2. Once I modified my code to have the JSF 2.2 approach work, the old Mojarra approach would break in strange ways.

Upvotes: 1

Related Questions