I have a template that's part of a form to edit some element. The action to be performed is different depending on the page in which it is included. So I pass the action method as a parameter:
<ui:param name="option" value="#{someOptionBean}" />
...
<ui:include src="/WEB-INF/jsf/value-edit.xhtml">
<ui:param name="controllerParam" value="#{optionController}" />
<ui:param name="elementParam" value="#{option}" />
<ui:param name="actionParam" value="updateOption" />
</ui:include>
or:
<ui:param name="property" value="#{somePropertyBean}" />
...
<ui:include src="/WEB-INF/jsf/value-edit.xhtml">
<ui:param name="controllerParam" value="#{propertyController}" />
<ui:param name="elementParam" value="#{property}" />
<ui:param name="actionParam" value="updateProperty" />
</ui:include>
and in value-edit.xhtml there is a command button:
<p:commandButton value="Update" action="#{controllerParam[actionParam](elementParam)}" />
So far everything works fine.
My problem is that now the action methods don't have the same number of parameters. They are:
public void updateOption(Option option) { ... }
public void updateProperty(Item item, Prop property) { ... }
so I now want to be able to also define the action parameters to have something like:
<ui:param name="actionParam" value="updateOption(option)" />
<ui:param name="actionParam" value="updateProperty(item, property)" />
or something like:
<ui:param name="method" value="updateProperty" />
<ui:param name="parameters" value="item, property" />
I've read the docs (Value and Method Expressions / Parameterized Method Calls) and I'm not sure if this is possible.
Is there any way to achieve this?
Finally I solved this creating a MethodExpression in the controller
Now the ui:include looks like:
<ui:param name="option" value="#{someOptionBean}" />
...
<ui:include src="/WEB-INF/jsf/value-edit.xhtml">
<ui:param name="actionParam" value="#{optionController.getUpdateAction()}" />
</ui:include>
and
<ui:param name="item" value="#{someItemBean}" />
<ui:param name="property" value="#{somePropertyBean}" />
...
<ui:include src="/WEB-INF/jsf/value-edit.xhtml">
<ui:param name="actionParam" value="#{propertyController.getUpdateAction()}" />
</ui:include>
In value-edit.xhtml the command button is:
<p:commandButton value="Update" action="#{actionParam}" />
The getUpdateAction creates the MethodExpression with the corresponding parameters for each controller:
public class OptionController {
...
public void updateOption(Option option) { ... }
public MethodExpression getUpdateAction() {
return createMethodExpression("#{optionController.updateOption(option)}", String.class, Option.class);
}
...
}
public class PropertyController {
...
public void updateProperty(Item item, Prop property) { ... }
public MethodExpression getUpdateAction() {
return createMethodExpression("#{propertyController.updateProperty(item, property)}", String.class, Item.class, Prop.class);
}
...
}
where createMethodExpression is:
public static MethodExpression createMethodExpression(String expression, Class<?> expectedReturnType, Class<?>... expectedParameterTypes) {
FacesContext facesContext = FacesContext.getCurrentInstance();
return facesContext.getApplication().getExpressionFactory().createMethodExpression(
facesContext.getELContext(), expression, expectedReturnType, expectedParameterTypes);
}
Related
I am facing a problem when trying to export a DataTable with some columns, both the DataTable and the Column components are composite elements, like this:
Custom dataTable XHTML (v:dataTable):
<cc:interface>
<cc:attribute name="value" required="true" />
<cc:attribute name="selectionMode" default="multiple" />
<cc:attribute name="selectionBean" />
<cc:attribute name="selectionProperty" />
<cc:facet name="header" />
</cc:interface>
<cc:implementation>
<p:dataTable id="teste" var="tableItem" value="#{cc.attrs.value}"
selection="#{cc.attrs.selectionBean[cc.attrs.selectionProperty]}"
rowKey="#{tableItem.id}" rowsPerPageTemplate="15, 30, 45"
paginator="true" rows="15"
emptyMessage="#{messages['dataTable.emptyMessage']}">
<cc:insertChildren />
<f:facet name="footer">
<p:commandButton value="#{messages['dataTable.exportExcel']}"
ajax="false">
<p:dataExporter type="xls" target="teste" fileName="export" />
</p:commandButton>`enter code here`
</f:facet>
</p:dataTable>
</cc:implementation>
Custom column XHTML (v:dataColumn):
<cc:interface
componentType="com.example.VDataColumn">
<cc:attribute name="value" />
</cc:interface>
<cc:implementation>
<c:choose>
<c:when test="#{cc.childCount gt 0}">
<cc:insertChildren />
</c:when>
<c:otherwise>
<h:outputText value="#{cc.attrs.value}" />
</c:otherwise>
</c:choose>
</cc:implementation>
The Column component is an extension of the org.primefaces.component.column.Column class:
package com.example.component;
import javax.faces.component.FacesComponent;
import javax.faces.component.NamingContainer;
import javax.faces.component.UINamingContainer;
import org.primefaces.component.column.Column;
#FacesComponent("com.example.VDataColumn")
public class VDataColumn extends Column implements NamingContainer {
#Override
public String getFamily() {
return UINamingContainer.COMPONENT_FAMILY;
}
}
The DataTable and Column are used as it follows:
<v:dataTable
value="#{testController.resultList}"
selectionBean="#{testController}"
selectionProperty="selectedList" selectionMode="multiple">
<p:column value="#{tableItem.id}" headerText="ID" />
<v:dataColumn value="#{tableItem.code}" headerText="Code" />
<v:dataColumn value="#{tableItem.nome}" headerText="Name" />
<v:dataColumn value="#{tableItem.desc}" headerText="Desc" />
</v:dataTable>
When I try to export the dataTable with the dataExporter that is inside the component, I get just one column in the XLS file, and it is only the p:column.
Debugging the primefaces DataExporter class, i noticed that the DataTable object contains 4 objects in the getChildren() method, one Column and 3 VDataColumn's, and only the Column object contains children itself.
Did anybody have the same issue? I am using Primefaces 4.0
I had same problem with a custom component. We resolve by extending the exporter and using custom validation for the component.
if (component instanceof UINamingContainer) {
UINamingContainer uiNamingContainer = (UINamingContainer) component;
UIComponent panel = uiNamingContainer.getFacet(UIComponent.COMPOSITE_FACET_NAME);
Collection<UIComponent> children = panel.getChildren();
for (UIComponent uiComponent : children) {
if (uiComponent instanceof javax.faces.component.html.HtmlOutputText) {
try {
uiNamingContainer.encodeBegin(context);
return super.exportValue(context, uiComponent);
} catch (IOException e) {
LOGGER.error(e);
} finally {
try {
uiNamingContainer.encodeEnd(context);
} catch (IOException e) {
LOGGER.error(e);
}
}
}
}
}
I want to update idOutput everytime that I change the value of selectOneMenu, but when it changes once a time to a value different of null I can't asign null another time, I think that is due to the required="true", but I don't know how to avoid the validation only in the ajax request.
Here is the code:
Bean:
#ViewScoped
#ManagedBean
public class ProbeNull implements Serializable
{
private static final long serialVersionUID = 1628174277372407129L;
private Boolean probe;
public ProbeNull()
{
super();
}
public void show()
{
System.err.println("Value : " + probe);
}
public void save()
{
System.err.println("Save : " + probe);
}
public Boolean getProbe()
{
return probe;
}
public void setProbe(Boolean probe)
{
System.err.println("Setter: " + probe);
this.probe = probe;
}
}
xhtml:
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui">
<h:head />
<h:body>
<h:outputText id="idOutput" value="#{probeNull.probe}" />
<h:form id="form">
<p:selectOneMenu id="select" required="true" value="#{probeNull.probe}">
<f:selectItem itemLabel="Select one" itemValue="#{null}" />
<f:selectItem itemLabel="Yes" itemValue="true" />
<f:selectItem itemLabel="No" itemValue="false" />
<p:ajax update=":idOutput" />
</p:selectOneMenu>
<p:commandButton value="Save" ajax="false" action="#{probeNull.save()}" />
</h:form>
<h:form>
<p:commandButton value="Show value" ajax="false" action="#{probeNull.show()}" />
</h:form>
</h:body>
</html>
How can avoid it?
You can do what you want by using two remoteCommand tags and JavaScript.
<p:remoteCommand name="makeSelection" process="select" update=":idOutput" />
<p:remoteCommand name="clearSelection" process="#this" update="select,:idOutput" >
<f:setPropertyActionListener value="#{null}" target="#{probeNull.probe}" />
</p:remoteCommand>
Now you can decide which one to call using a javascript funcion
<p:selectOneMenu id="select" required="true" value="#{probeNull.probe}" onchange="selectFunction(this)">
...
function selectFunction(el){
//if el.value is empty you call clearSelection();
//else you call makeSelection();
}
Don't forget to delete the <p:ajax update=":idOutput" />
In my jsf I use :
<f:param name="parm1" value="#{templateGP01MB.stringAnnId}" />
and receive a null parameter in my Managed Bean.
If I write the following :
<f:param name="parm1" value="5" />
I got 5 in my MBean.
Here is my code for my JSF:
<p:imageSwitch effect="turnDown" speed="10000">
<ui:repeat value="#{galerieFloMB.stringArray}" var="image" >
<h:commandLink value="Voir l'annonce correspondante" actionListener="#{templateGP01MB.actionListener}" action="#{templateGP01MB.action}" >
<!-- <p:graphicImage value="/resources/images/download/#{image}" width="300" height="300" onclick="visualisationAnnonce('#{image}');" /> -->
<p:graphicImage value="/resources/images/download/#{image}" width="300" height="300" />
<f:attribute name="attributeName1" value="#{image}" />
<f:param name="parm1" value="#{templateGP01MB.stringAnnId}" />
</h:commandLink>
</ui:repeat>
</p:imageSwitch>
</h:form>
And here is my code for my MBean TemplateGP01MB.java:
public void actionListener(ActionEvent event) {
photo = (String) event.getComponent().getAttributes().get("attributeName1");
annonceEtendue = getAnnonceEtendueFacade().searchByPhoto(photo);
}
public String action() {
return "visualisationAnnonce";
}
In the MBean VisualisationAnnonceMB.java, I got :
FacesContext fc = FacesContext.getCurrentInstance();
Map<String, String> params = fc.getExternalContext().getRequestParameterMap();
String parm1 = params.get("parm1");
Integer integerParm1 = Integer.parseInt(parm1);
And I got a Null pointer exeption cause parm1 = null...
Sure I am a newbie and I don't really understand evrything about jsf lyfecycle.
If someone can HELP me, it would be great :)
Thanks in advance
GLO
I work with Mojarra 2.1.3.
When the user click on button "refresh don't work", it refresh the content of the ui:repeat.I expect the checkbox to be checked, just as at the initialization.
What I've found: If I remove h:head in the facelet "refresh don't work" works... Any idea ?
The facelet:
<h:head></h:head>
<h:body>
<h:form id="myForm" >
<h:panelGroup id="panelToRefreshOutsideRepeat">
<ui:repeat value="#{sandbox.columns}" var="column">
<h:panelGroup id="panelToRefreshInsideRepeat">
<h2>composite onlyCheckbox:</h2>
<trc:onlyCheckbox value="#{column.value}" />
<br />
<h2>composite onlyInputText:</h2>
<trc:onlyInputText value="#{column.value}" />
<br />
<br/>
<h:commandButton value="Refresh don't work" >
<f:ajax render="panelToRefreshInsideRepeat" />
</h:commandButton>
<h:commandButton value="Refresh work" >
<f:ajax render=":myForm:panelToRefreshOutsideRepeat" />
</h:commandButton>
</h:panelGroup>
<br/>
</ui:repeat>
</h:panelGroup>
The composite for onlyCheckbox and onlyInputText:
<composite:interface>
<composite:attribute name="value"
type="boolean"/>
</composite:interface>
<composite:implementation>
boolean: <h:selectBooleanCheckbox value="#{cc.attrs.value}" />
<!-- for onlyInputText h:inputText instead of h:selectBooleanCheckbox -->
boolean value: #{cc.attrs.value}
</composite:implementation>
and the backing bean:
#ManagedBean
#RequestScoped
public class Sandbox {
public List<Column> columns = Arrays.asList(new Column(true));
public List<Column> getColumns() {
return columns;
}
public void setColumns(List<Column> columns) {
this.columns = columns;
}
public class Column {
private boolean value;
public Column(boolean value) {
this.value = value;
}
public void setValue(boolean value) {
this.value = value;
}
public boolean getValue() {
return this.value;
}
}
}
I can reproduce your problem even on latest Mojarra 2.1.4. It works fine if the checkbox is not inside a composite. This is a bug in Mojarra's <ui:repeat>. It is totally broken in Mojarra. It works perfectly fine on MyFaces 2.1.3.
You have 2 options:
Replace Mojarra by MyFaces.
Use an UIData component instead of <ui:repeat>, e.g. <h:dataTable>, <t:dataList>, <p:dataList>, etc.
I'm having some problems with bean initialization. I have an "edit" form to update
some user data. The user is previously created in the database and
I retrieve successfuly the data and put it in the form.
This is the form with id parameter 1 (/cms/admin/users/edit.jsf?id=1):
<h:panelGrid styleClass="general-form" columns="3" id="ajaxForm">
<h:outputLabel for="name" value="#{cms['users.add.name']}" />
<p:inputText required="true" id="name"
value="#{editUserController.user.name}"
label="#{cms['users.add.name']}">
<f:validateLength minimum="3" />
</p:inputText>
<p:message for="name" />
<h:outputLabel value="#{cms['users.add.lastname']}" />
<p:inputText id="lastname" label="#{cms['users.add.lastname']}"
required="true" value="#{editUserController.user.lastname}">
<f:validateLength minimum="3" />
</p:inputText>
<p:message for="lastname" />
<h:outputLabel value="#{cms['users.add.username']}" />
<p:inputText id="username" required="true"
label="#{cms['users.add.username']}"
value="#{editUserController.user.username}">
<f:validateLength minimum="3" />
</p:inputText>
<p:message for="username" />
<h:outputLabel value="#{cms['users.add.password']}" />
<p:password id="password" required="true"
value="#{editUserController.user.password}"
promptLabel="#{cms['users.error.enterPassword']}"
weakLabel="#{cms['users.error.weakPassword']}"
goodLabel="#{cms['users.error.goodPassword']}"
strongLabel="#{cms['users.error.strongPassword']}"
binding="#{password}" label="#{cms['users.add.password']}">
<f:validateLength minimum="6" />
</p:password>
<p:message for="password" />
<h:outputLabel value="#{cms['users.add.confirmPassword']}" />
<p:password id="passwordConfirm" required="true"
value="#{editUserController.confirmPassword}"
promptLabel="#{cms['users.error.enterPassword']}"
weakLabel="#{cms['users.error.weakPassword']}"
goodLabel="#{cms['users.error.goodPassword']}"
strongLabel="#{cms['users.error.strongPassword']}"
label="#{cms['users.add.confirmPassword']}">
<f:validateLength minimum="6" />
<f:validator validatorId="passwordValidator" />
<f:attribute name="password" value="#{password.value}" />
</p:password>
<p:message for="passwordConfirm" />
<h:outputLabel value="#{cms['users.add.active']}" />
<h:selectOneMenu value="#{editUserController.user.active}">
<f:selectItem itemValue="true" itemLabel="Sí" />
<f:selectItem itemValue="false" itemLabel="No" />
</h:selectOneMenu>
<h:outputText value="" />
<h:outputLabel value="#{cms['users.add.role']}" />
<h:selectOneMenu value="#{editUserController.securityRole}">
<f:selectItems value="#{editUserController.securityRoles}" />
</h:selectOneMenu>
<h:outputText value="" />
<h:outputText value="" />
<p:commandButton action="#{editUserController.saveUser}"
style="margin-top:20px;" ajax="false"
value="#{cms['general.save']}"></p:commandButton>
This is the Managed Bean (EditUserController.java):
public class EditUserController extends GeneralController implements
Serializable {
private static final long serialVersionUID = 1L;
#ManagedProperty("#{param.id}")
private Integer id;
private UserService userService;
private SecurityRoleService securityRoleService;
private User user;
private String confirmPassword;
private Map<String, SecurityRole> securityRoles;
private String securityRole;
#PostConstruct
public String checkUser() {
try {
FacesContext facesContext = FacesContext.getCurrentInstance();
this.id = Integer.parseInt(facesContext.getExternalContext().getRequestParameterMap().get("id"));
setUser(userService.findById(id));
Map<String, SecurityRole> secRoles = new ListUtils<String>().toMap(
securityRoleService.findAll(0, 30, "", ""), "name");
setSecurityRoles(secRoles);
setSecurityRole(user.getSecurityRole().getId().toString());
return "edit";
} catch (EntityNotFoundException e) {
return FATAL;
} catch (NoSuchMethodException e) {
return FATAL;
} catch (InvocationTargetException e) {
return FATAL;
} catch (IllegalAccessException e) {
return FATAL;
}
}
public String saveUser() {
try {
System.out.println("User saved!!");
return SUCCESS;
} catch (Exception e) {
FacesContext.getCurrentInstance().addMessage(
null,
new FacesMessage(MessageProvider.getMessageProvider()
.getValue("cms", "general.error")));
return ERROR;
}
}
//getters and setters ...
With the #Postconstruct method I initialize the user variable according to the id param.
Then the form is displayed correctly with all data prefilled. But when I click in the
commandButton I get an exception:
Error creating bean with name 'editUserController': Invocation of init method failed; nested exception is java.lang.NumberFormatException: null
I understand where is the error. In the #Postconstruct method the variable id has not
been "initialized" because there is not any id parameter in the request. I know I can solve this
scoping the bean to session or application, but it does not make any sense to have the bean there
just to perform this simple action.
How can I do this simple form? I just want to get an id parameter, show a form with prefilled data
then give the posibility to change the data an finally submit the form to save the new values
into the DB
Thank you all
If putting the bean in the view scope and using <f:viewParam> is not an option, then you'd need to pass the parameter back to the next request by including it as <f:param> in the UICommand component:
<p:commandButton action="#{editUserController.saveUser}"
style="margin-top:20px;" ajax="false"
value="#{cms['general.save']}">
<f:param name="id" value="#{editUserController.id}" />
</p:commandButton>
See also:
ViewParam vs #ManagedProperty(value = "#{param.id}")
Communication in JSF 2.0 - Processing GET request parameters