JSF Access viewParams from within a component - jsf

I have a component to display a entry. This component can appear multiple times within the same page, and therefore just takes an entity, passes it to the component's backing bean where all the magic happens.
The component's appearence can be affected by various parameters.
Now, it is a requirement, that no matter if I have 1 or 10 of those components, the "same" view-state should be recoverable using a deeplink.
Let's say my component looks like this:
<composite:interface componentType="itemView" >
<composite:attribute name="item" required="true" shortDescription="The item to display."/>
<composite:attribute name="urlPrefix" required="true" />
</composite:interface>
<composite:implementation>
<f:event type="preRenderComponent" listener="#{cc.init}" />
....
</composite:implementation>
it has a corresponding backing Bean, I can access. (leaving this out, as its not required)
Now, these components are embedded on a page, that looks like this:
<f:metadata>
<f:viewParam name="itemType"
value="#{listController.itemType}"></f:viewParam>
</f:metadata>
<ui:define name="content">
<!-- multiple occurences, ui repeat over list of items -->
<my:itemView item="#{currentItem}" urlPrefix="#{listController.nextLetter}">
As already mentioned, the state of every item can be affect, and the changed state musst be accessible by a deeplink. So, my idea was, to use <h:link> inside the component, prefix every attribute and use includeViewParams="true", so that every (not defaulted) state is part of the url.
<composite:interface componentType="itemView" >
<composite:attribute name="item" required="true" shortDescription="The item to display."/>
<composite:attribute name="urlPrefix" required="true" />
</composite:interface>
<composite:implementation>
<f:event type="preRenderComponent" listener="#{cc.init}" />
<h:link includeViewParams="true">
<f:param name="#{cc.attrs.urlPrefix}size" value="#{cc.largerSize}" />
larger
</h:link>
....
</composite:implementation>
Just as for the "larger" link, there are upto 10 links, providing different options.
The problem: include-view-params does include all the view-params from within the same component, and its parent (itemType), but NOT from other components. So, basically changing the style of "one" component, will drop the parameters of another component.
Expected:
The url already looks like ?itemType=something&asize=5&abackground=green
Clicking the "larger" link on the b component will give me : ?itemType=something&asize=5&abackground=green&bsize=5
Whats happening:
After clicking the link, the url looks like: ?itemType=something&bsize=5
so the attributes for "a" are dropped.
I noticed, that if i extend the <f:metadata> object on the listing-page with something like <f:viewParam name="asize" /> the aSize parameter will be preserved, when clicking something from within "b" - but of course if i need to list all parameters like THAT, i dont need components, because i can not predict the number of items.
So, i tried to add this just right inside the component (like the f:event): <f:viewParam name="#{cc.urlPrefix}size" /> - but that did not work.
Any Ideas on that (or alternative solutions? Note: Saving the view States in the database, and simple using a "single" id to reference that state is basically bad, because that would cause a big amount of datapolution. The View-State needs to stay transient, but recoverable :0) )

Finally i found a solution. It's not the best, but it works at least ;)
I created a method on the component's bean, that will return a query string, containing all values ecxcept the one(s) passed as a paremeter.
So, whenever I want to change the parameter asize=5, i call this function with param size and append the new parameter (Function will take care of excluding this parameter only, when the prefi matches, so no other components style would be affected.):
public String buildUrlWithout(String keyString) {
List<String> keys = ConversionHelper.Explode(keyString, ",");
// get all parameters from the url.
String prefix = this.getAttributes().get("urlPrefix").toString();
ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
Map<String, String> params = externalContext.getRequestParameterMap();
List<KeyValuePair<String, String>> result = new LinkedList<KeyValuePair<String, String>>();
outerfor: for (Map.Entry<String, String> kvp : params.entrySet()) {
for (String s : keys) {
if (kvp.getKey().equals(prefix + s)) {
continue outerfor;
}
}
// keep param
result.add(new KeyValuePair<String, String>(kvp.getKey(), kvp.getValue()));
}
String qStr = KeyValuePair.toQueryString(result);
return qStr;
}
KeyValuePair ist just a Helper-Class that behaves like a Map<S,T> but offers several functions like join(), split() etc.. (in this case toQueryString() is used)
Finally, from within a component, I use this like this:
<h:outputLink
value="#{cc.buildUrlWithout('size')}&#{cc.attrs.urlPrefix}size={cc.size +1}">
larger
</h:outputLink>
<h:outputLink
value="#{cc.buildUrlWithout('size')}&#{cc.attrs.urlPrefix}size={cc.size -1}">
smaller
</h:outputLink>
<h:outputLink
value="#{cc.buildUrlWithout('background,border')}&#{cc.attrs.urlPrefix}background=red&#{cc.attrs.urlPrefix}border=green">
red background + green border
</h:outputLink>

Related

Using findComponent on preRenderView listener

On the JSF page:
<f:event type="preRenderView" listener="#{backingBean.test()}" />
<h:inputHidden id="inputHiddenId" value="dummy" />
On the backingBean:
public void test() {
System.out.println("test");
FacesContext fc = FacesContext.getCurrentInstance();
UIComponent comp = fc.getViewRoot().findComponent("inputHiddenId");
if (comp != null) {
System.out.println("comp " + comp); // Does not print
}
}
The phaseListener shows that test() is called during render response phase:
START PHASE RESTORE_VIEW 1
END PHASE RESTORE_VIEW 1
START PHASE RENDER_RESPONSE 6
test
END PHASE RENDER_RESPONSE 6
However, it is not finding the inputHiddenId component. It seems that the component is not available during this time. Does it need to be on a postback? How can I access the component when first loading the page? Sorry, I am not too familiar with the intricacies of the JSF lifecycle. If anyone could shed some light on this, it would be greatly appreciated.
Reposting the problem:
The real problem is that I have a form with several fields. After the form is submitted, users can still edit certain fields which has the effect of resetting other sections on the form and making it invalid. Since it is a long form, users should be able to save the form even though it fails validation, and return to it later. So when loading the page, I'd like it to display inline validation on the fields. We are using JBoss Seam. The validation method displays the errors using
StatusMessages.instance().addToControl("inputHiddenId", severity, message)
The validation method is called from the page.xml using
<action execute="#{backingBean.pageInitWithValidation()}" on-postback="false" />
However this doesn't display the error message inline on the ui component
<s:div id="errorMessages">
<h:inputHidden id="inputHiddenId" value="dummy" />
<h:messages for="inputHiddenId" />
</s:div>
It shows it instead at the top in the global messages
<h:messages globalOnly="true" />
That's why I tried to use the preRenderView, but that had the same effect.
I'm currently using a second method with the body onload event along with a4j:jsFunction, but it is not very clean.
<a4j:jsFunction name="validate"
action="#{backingBean.validate()}" render="formId" />
<h:body onload="validate()">

Use JSF form to create an object with a list of other objects

I am using JSF 2 with Glassfish 4. I would like to create an object that has a collection of objects as one of its fields. When searching, I can only find ways to display a collection field in a JSF form. Here I want the opposite: allow the user to populate this collection while creating the parent object. Simplified example below:
Parent Object: Account
public class Account {
private String accountName;
private List<Order> orderList = new ArrayList<Order>();
public String save() {
System.out.println(accountName);
System.out.println(orderList);
return "";
}
// Constructors, getters and setters below.
}
Child Object: Order
public class Order {
private String orderName;
private Integer orderCost;
// Constructors, getters and setters below.
}
JSF Page Body
The idea for the form was taken from BalusC's answer here.
<h:body>
<h1>Create Account</h1>
<h:form>
<h:panelGrid>
Account Name:
<h:inputText value="#{account.accountName}" />
<ui:repeat value="#{account.orderList}" varStatus="loop">
Order Name:
<h:inputText value="#{account.orderList[loop.index]}" />
Order Cost:
<h:inputText value="#{account.orderList[loop.index]}" />
</ui:repeat>
</h:panelGrid>
<h:commandButton action="#{account.save}" value="Create" />
</h:form>
</h:body>
I run into a few problems:
It's impossible for me to have a set number of Orders displayed. (ex: max 5 per new account). An input field is only displayed if the List already has some objects. This makes sense, but I would like to present the user with X blank lines they can fill in.
I am unable to expose both the orderName and orderCost fields at once to the user.
Later on I would like to add a commandButton that adds another row of inputText fields in the UI so the user can add however many orders to an account as they want.
Any help greatly appreciated. Happy to answer questions for anything I missed. Thank you!
After BalusC's help I made the following changes and I now have the behaviour that I want:
// Prop up the array so the desired number of fields appears in the UI
#PostConstruct
public void prepare() {
orderList.add(new Order());
orderList.add(new Order());
orderList.add(new Order());
}
Iterate through the list of empty Order objects. No data is disabled since all field values in the Order objects are null. Also, since I created the objects in #PostConstruct, the user's changes are easily saved on Submit.
<ui:repeat value="#{account.orderList}" var="order">
Order Name:
<h:inputText value="#{order.orderName}" />
Order Cost:
<h:inputText value="#{order.orderCost}" /><br/>
</ui:repeat>
As to preparing a fixed amount of items and adding a new item, just add a new item to the list.
orders.add(new Order());
Do this in #PostConstruct to prepare the items on page load and do the same in "Add" button.
As to accessing properties, you don't need the varStatus/index trick as you've there a collection of mutable objects, not a collection of immutable objects (where the question you found was all about).
<ui:repeat value="#{bean.orders}" var="order">
<h:inputText value="#{order.name}" />
<h:inputText value="#{order.cost}" />
</ui:repeat>
If you really insisted to, you could have done #{bean.orders[loop.index].name}, but as said, this is unnecessary.

How to evaluate a FacesComponent property inside a custom component

Lately, I've been working on a dynamic form project but is stop with a custom component problem. What I have:
A faces component for formField:
#FacesComponent(value = "formField")
public class FormFieldCompositeComponent {
private boolean email;
}
A custom component jsf:
<o:validator for="email_text" validatorId="emailValidator" disabled="#{not cc.email}" />
OR
<c:if test="#{not cc.email}">
<f:validator for="email_text" validatorId="emailValidator"></f:validator>
</c:if>
OR
<f:validator disabled="#{not cc.email}" for="email_text" validatorId="emailValidator"></f:validator>
And the validator:
#FacesValidator("emailValidator")
public class EmailValidator implements Validator { }
My problems are:
1.) If I use an ordinary f:validator, like the one I use above and then use c:if to enable/disable it, then it will not work. According to some articles I've read it's because f:validator validates on build time, not on render time.
2.) If I use o:validator, it works but the problem is every time you hit submit a new line of invalid email error is added to p:messages. Example I clicked submit button 3 times, then I get 3 times the email error.
Any idea?
More info (anatomy of the project)
Example I have a page user with field email, it will include the following custom components:
+user.xhtml
+formPanel
+formField (this is where the validator is defined)
+formButtons (the action button)
+p:messages is defined
user.xhtml
<formPanel>
<formField field="email" />
<formButtons />
</formPanel>
Command button is like (formButtons):
<p:commandButton id="saveButton" rendered="#{cc.attrs.edit}"
value="#{messages['action.save']}"
action="#{cc.attrs.backingBean.saveOrUpdate()}" icon="ui-icon-check"
ajax="#{cc.attrs.ajaxSubmit}">
<c:if test="#{cc.attrs.backingBean.lcid != null}">
<f:param name="cid" value="#{cc.attrs.backingBean.lcid}" />
</c:if>
</p:commandButton>
The p:messages as defined on formPanel:
<p:messages id="formMessages" showDetail="true" showSummary="false" redisplay="false"></p:messages>
Note:
1.) What I've noticed is that the validator is called n times, where n is the number of submit or click done.
xhtml - https://github.com/czetsuya/crud-faces/blob/master/crud-faces/src/main/webapp/administration/user/user.xhtml
the tags - https://github.com/czetsuya/crud-faces/tree/master/crud-faces/src/main/webapp/resources/tags
bean component - https://github.com/czetsuya/crud-faces/tree/master/crud-faces/src/main/java/org/manaty/view/composite
Seems like there's no chance for the f:validator so I push through o:validator and come up with a workaround. Need to catch if the error is already in the FacesMessages list:
boolean match = false;
for (FacesMessage fm : context.getMessageList()) {
if (fm.getDetail().equals(message)
|| fm.getSummary().equals(message)) {
match = true;
break;
}
}
if (!match) {
throw new ValidatorException(facesMessage);
}

JSF-2 Custom tag to change the styleClass for the child's element

I use JSF 2. I need to write a custom tag which changes the style class for messages inside of it.
So if I have such code:
<my:errorGrid errorClass="#{richBean.erClass}" >
<my:inputField id="pid1" value="#{richBean.pid}" fsType="pid">
</my:inputField>
<h:message for="pid1" ajaxRendered="true"/>
</my:errorGrid>
Error message should be outputted with additional styleClass. The name of this styleClass is taken from the attribute errorClass of the custom tag my:errorGrid
As I undrstand I need to use vcp:body tag in template and process children in encodeChildren() method. Please, could anybody provide me some hints how to realize it? Maybe some advice or links.
Why not using a composite component?
errorGrid.xhtml
<composite:interface>
<composite:attribute name="errorClass"/>
</composite:interface>
<composite:implementation>
<my:inputField id="pid1" value="#{richBean.pid}" fsType="pid">
</my:inputField>
<h:message for="pid1" ajaxRendered="true" errorClass="#{cc.attrs.errorClass}"/>
</composite:implementation>
If you have a classical component though, you simply have to call HtmlMessage.setErrorClass() with your error class before rendering. e.g:
for (UIComponent child : getChildren()){
if(child instanceof HtmlMessage) {
HtmlMessage msgComp = (HtmlMessage) child;
msgComp.setErrorClass(getErrorClass());
}
}

JSF Temporary Variable

Is there a way to temporarily save the value of calcuation in a JSF page?
I want to do the following without calculating twice:
<h:outputText value="#{complexModel.currencyAmount.currency}">
<h:outputText value="#{complexModel.currencyAmount.amount}">
I've tried using the alias bean but I get an error saying java.lang.IllegalArgumentException - row is unavailable.
e.g.
<t:aliasBean id="bean" alias="#{bean}" value="#{complexModel.currencyAmount}">
<h:outputText value="#{bean.currency}">
<h:outputText value="#{bean.amount}">
</t:aliasBean>
Thanks.
Two ways (at least):
Using lazy-init field of your complexModel. something like:
private Currency currencyAmount;
public Currency getCurrencyAmount() {
if (currencyAmount == null) {
currencyAmount = calculateCurrencyAmount();
}
return currencyAmount;
}
Using the JSTL <c:set> tag:
(the namespace first)
xmlns:c="http://java.sun.com/jstl/core"
then
<c:set var="varName" value="#{complexModel.currencyAmount}" />
And then the calculated value will be accessible through #{varName}.
In JSF 2.0,you can use <ui:param/> which it's powerful.
<ui:param name="foo" value="#{zhangsan.foo}"/>

Resources