I have problem using JSF 2.0. The command button doesn't call the bean, I have already read the balusc answer commandButton/commandLink/ajax action/listener method not invoked or input value not updated but I don't think I'm encountering those cases,
this is the code I'm using:
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:p="http://primefaces.org/ui"
xmlns:c="http://java.sun.com/jstl/core">
....
<ui:repeat var="skill" value="#{skillsView.skills}">
<h:form>
<h:commandButton value="#{skill}"
action="#{skillsController.removeSkillFromPublication}" ajax="true">
<f:param name="theskill" value="#{skill}" />
</h:commandButton>
</h:form>
</ui:repeat>
</ui:composition>
UPDATE 1 :
this my controller
#Controller
#Scope(value = WebApplicationContext.SCOPE_REQUEST)
public class SkillsController extends BController {
private static final Logger logger = LoggerFactory
.getLogger(SkillsController.class);
public void removeSkillFromPublication() {
logger.info("Deleting : " + getParameter("theskill"));
publicationService.removeSkillFromPublication(publicationDetailView
.getPublicationFullView().getId(), getParameter("theskill"));
skillsView.setSkills(publicationService
.getSkillsFromPublication(publicationDetailView
.getPublicationFullView().getId()));
}
}
i don't get the the logger neither the task is executed , i already tested with actionlistner with controller with actionevent and this doesent work
UPDATE 2 :
It's working with changing the SCOPE of the skillsView To WebApplicationContext.SCOPE_SESSION , anybody know why ?!!
#Controller
#Scope(value = WebApplicationContext.SCOPE_SESSION)
public class SkillsView {
List<String> skills;
private String currentSkill;
public List<String> getSkills() {
return skills;
}
public void setSkills(List<String> skills) {
this.skills = skills;
}
public String getCurrentSkill() {
return currentSkill;
}
public void setCurrentSkill(String currentSkill) {
this.currentSkill = currentSkill;
}
The h:commandButton attribute action requires you to return a String
Taken from the jsf-toolbox:
action
The action attribute accepts a method-binding expression for a backing bean action method to invoke when this component is activated by the user. An action method must be a public method with no parameters that returns a String. The returned string represents the logical outcome of the action (eg. "success", "failure", etc. ) and is used by the JavaServer Faces MVC framework to determine which view to display next.
[bolding by me]
Instead use the actionListener property:
<h:commandButton actionListener="${skillsController.removeSkillFromPublication()}" ajax="true">
<f:param name="theskill" value="#{skill}" />
</h:commandButton>
Keep in mind you will have to change the method-signature to have an ActionEvent as parameter:
public void removeSkillFromPublication(ActionEvent e) {
//...
See the toolbox:
actionListener
The actionListener attribute accepts a method-binding expression for a backing bean action listener method that will be notified when this component is activated by the user. An action listener method must be a public method with an ActionEvent parameter and a void return type.
Alternatively you could have your removeSkillFromPublication() return a String. But that seems to be the wrong approach here.
Related
I have a <h:selectManyCheckbox> that has a required-validation on. If I submit the form, I get a validation error when nothing is selected. So far, this ist expected. However, if I do an ajax update on the checkbox then, I get a ClassCastException. But only if empty values are treated as null.
So, I have the following setup. In the web.xml I set
<context-param>
<param-name>javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL</param-name>
<param-value>true</param-value>
</context-param>
Then I have an xhtml-page like this:
<h:form id="main">
<h:selectManyCheckbox id="value" value="#{testcb.selected}" required="true" requiredMessage="Select at least one entry">
<f:selectItems value="#{testcb.available}"/>
</h:selectManyCheckbox>
<div><h:message for="value" style="color:red;"/></div>
<h:outputLabel for="checkit" value="Enter some text: "/>
<h:inputText id="checkit" value="#{testcb.text}">
<f:ajax event="change" execute="#this" render=":main:value"/>
</h:inputText>
<div><h:commandButton type="submit" value="Submit" action="#{testcb.action}"/></div>
</h:form>
And this backing bean:
#Named("testcb")
#SessionScoped
public class TestCBBean implements Serializable {
private final Set<TestValue> available = EnumSet.allOf(TestValue.class);
private final Set<TestValue> selected = EnumSet.noneOf(TestValue.class);
private String text;
public void action() {}
public Set<TestValue> getAvailable() { return available; }
public void setAvailable(Set<TestValue> available) {
this.available.clear();
this.available.addAll(available);
}
public Set<TestValue> getSelected() { return selected; }
public void setSelected(Set<TestValue> selected) {
this.selected.clear();
this.selected.addAll(selected);
}
public String getText() { return text; }
public void setText(String text) { this.text = text; }
}
And this enum:
public enum TestValue { ONE, TWO, THREE }
I am running this in Wildfly 26.0.1-Final (JavaEE 8). But this also happens in older versions (like Wildfly 15). What I am doing:
enter some text and leave the box: an ajax update runs setting the value successfully in the model
I press submit: the validation error for the empty checkboxes pops up
I modify the text in the input and leave the box: the ajax update results in the following Exception:
java.lang.ClassCastException: class java.lang.String cannot be cast to class [Ljava.lang.Object; (java.lang.String and [Ljava.lang.Object; are in module java.base of loader 'bootstrap')
com.sun.jsf-impl#2.3.17.SP01//com.sun.faces.renderkit.html_basic.MenuRenderer.getSubmittedSelectedValues(MenuRenderer.java:508)
com.sun.jsf-impl#2.3.17.SP01//com.sun.faces.renderkit.html_basic.SelectManyCheckboxListRenderer.encodeEnd(SelectManyCheckboxListRenderer.java:89)
javax.faces.api#3.1.0.SP01//javax.faces.component.UIComponentBase.encodeEnd(UIComponentBase.java:600)
javax.faces.api#3.1.0.SP01//javax.faces.component.UIComponent.encodeAll(UIComponent.java:1655)
com.sun.jsf-impl#2.3.17.SP01//com.sun.faces.context.PartialViewContextImpl$PhaseAwareVisitCallback.visit(PartialViewContextImpl.java:628)
com.sun.jsf-impl#2.3.17.SP01//com.sun.faces.component.visit.PartialVisitContext.invokeVisitCallback(PartialVisitContext.java:159)
javax.faces.api#3.1.0.SP01//javax.faces.component.UIComponent.visitTree(UIComponent.java:1457)
javax.faces.api#3.1.0.SP01//javax.faces.component.UIComponent.visitTree(UIComponent.java:1469)
javax.faces.api#3.1.0.SP01//javax.faces.component.UIForm.visitTree(UIForm.java:355)
On the ajax update the checkboxes are not submitted. But they seem to contain an empty string as submitted value from the validation step before.
When setting the context parameter to false this works. But I want to keep it on true. Any ideas how I could work around this problem?
Reproduced. This is indeed a bug in Mojarra.
It boils down to that the following method in UIInput superclass ...
#Override
public Object getSubmittedValue() {
if (submittedValue == null && !isValid() && considerEmptyStringNull(FacesContext.getCurrentInstance())) {
return "";
} else {
return submittedValue;
}
}
... is not overridden in UISelectMany superclass in such way that it returns new String[0] instead of "". This was an oversight during implementing Faces issue 671.
I have fixed it in Mojarra issue 5081.
In the meanwhile, until you can upgrade to the Mojarra version containing the fix, you can temporarily work around it by copy pasting the entire source code file of UISelectMany into your project while maintaining the package and adding the following method to it:
#Override
public Object getSubmittedValue() {
Object submittedValue = super.getSubmittedValue();
return "".equals(submittedValue) ? new String[0] : submittedValue;
}
Having a simple .xhtml, mainly with a button going outside:
<html
xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
>
<f:view>
<h:body>
<h:form>
<h:messages id="messages" />
<h:outputLabel for="name-field" /><h:inputText id="name-field" value="#{mySession.name}" />
<h:commandButton value="Submit" action="#{myService.goOutside}" />
</h:form>
</h:body>
</f:view>
</html>
And a simple backing bean supporting it:
#Named
#SessionScoped
public class MySession implements Serializable {
private String name = "My Name";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
private static final Logger LOG = Logger.getLogger(MySession.class.getName());
#PostConstruct
public void init() {
LOG.info("MySession initialized!");
}
}
And the RequestScoped bean supporting the goOutside service method:
#Named
#RequestScoped
public class MyService {
private static final Logger LOG = Logger.getLogger(MyService.class.getName());
public String goOutside() throws IOException {
LOG.info("Enter goOutside");
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
ec.invalidateSession(); // To make sure that old session with old user defaults are not retained!
ec.redirect("https://en.wikipedia.org/");
LOG.info("Exit goOutside");
return null;
}
}
This works as expected - after "Exit goOutside" is logged, I do not see "MySession initialized!" until and unless I revisit my original xhtml.
In the context of my complete code - which down to those portions looks essentially the same, I do not get that behaviour: the MySession bean gets reinitialized right after the redirect, and before I revisit the original URL.
I am still trying to strip down the original code until it works finding out the point at which it should break with minimal amount of code - but I feel it might take me a while due to the way it is woven into the rest.
If any idea in what this issue would be introduced - this would shorten my research. Or I could rephrase my question as: "What do I add into above code so that I see MySession initialized right away?".
My actual scenario is a simple JSF project that uses Mojarra 2.2.5.
I'm trying to get programmatically a component instance (via "findComponent" method or binding ...) and set some attributes (click on "ChangeColor" button).
After that, using any other action (for example clicking on "Send" button), the previous changes are ignored !!
It seems that changeColor method don't update the ViewState !
The sample code is the following:
<h:form id="form">
<h:panelGrid columns="1">
<h:inputText id="input" binding="#{page9.input}"/>
<h:commandButton value="Change BackColor" action="#{page9.changeColor}"/>
<h:commandButton value="Send" action="#{page9.dummy}" />
</h:panelGrid>
</h:form>
And the relative RequestScope bean
#ManagedBean(name="page9")
#RequestScoped
public class Page9 implements Serializable {
private static final long serialVersionUID = 1L;
private HtmlInputText input;
public HtmlInputText getInput() {
return input;
}
public void setInput(HtmlInputText input) {
this.input = input;
}
public void changeColor(){
FacesContext fc = FacesContext.getCurrentInstance();
HtmlInputText hit = (HtmlInputText) fc.getViewRoot().findComponent(":form:input");
hit.setStyle("background-color:blue");
}
public void dummy(){
}
}
Some important considerations:
1) I must use RequestScope for compatibility reasons.
2) Setting javax.faces.PARTIAL_STATE_SAVING to "false" all works fine (!).
3) Trying to use MyFaces 2.2.3 libraries all works fine (!).
What do you think about ?
Thanks.
Suppose I want to navigate in my application, and include different facelet pages dynamically. I have a commandLink like this:
<h:commandLink value="Link" action="#{navigation.goTo('someTest')}">
<f:ajax render=":content" />
</h:commandLink>
And this is where I include the facelet:
<h:form id="content">
<ui:include src="#{navigation.includePath}" />
</h:form>
The Navigation class:
public class Navigation {
private String viewName;
public void goTo(String viewName) {
this.viewName = viewName;
}
public String getIncludePath() {
return resolvePath(viewName);
}
}
I have seen similar examples, but this doesn't work of course. As ui:include is a taghandler, the include happens long before my navigation listener is invoked. The old facelet is included, instead of the new. So far I get it.
Now to the headache part: How can I dynamically include a facelet, based on an actionListener? I tried to include the facelet in a preRender event, and a phaseListener before RENDER_RESPONSE. Both work, but in the event listener I can't include a facelet which contains an other preRender event, and in the phaseListener I get duplicate Id's after some clicks in the included facelet. However, inspecting the component tree tells me, there are no duplicate components at all. Maybe these two ideas were not to good at all..
I need a solution, where the page with the ui:include, or the Java class which includes the facelet, doesn't have to know the pages, which will be included, nor the exact path. Did anybody solve this problem before? How can I do it?
I am using JSF 2.1 and Mojarra 2.1.15
All you need to reproduce the Problem is this bean:
#Named
public class Some implements Serializable {
private static final long serialVersionUID = 1L;
private final List<String> values = new ArrayList<String>();
public Some() {
values.add("test");
}
public void setInclude(String include) {
}
public List<String> getValues() {
return values;
}
}
This in your index file:
<h:head>
<h:outputScript library="javax.faces" name="jsf.js" />
</h:head>
<h:body>
<h:form id="topform">
<h:panelGroup id="container">
<my:include src="/test.xhtml" />
</h:panelGroup>
</h:form>
</h:body>
And this in text.xhtml
<ui:repeat value="#{some.values}" var="val">
<h:commandLink value="#{val}" action="#{some.setInclude(val)}">
<f:ajax render=":topform:container" />
</h:commandLink>
</ui:repeat>
That's enough to produce an error like this:
javax.faces.FacesException: Cannot add the same component twice: topform:j_id-549384541_7e08d92c
For OmniFaces, I've also ever experimented with this by creating an <o:include> as UIComponent instead of a TagHandler which does a FaceletContext#includeFacelet() in the encodeChildren() method. This way the right included facelet is remembered during restore view phase and the included component tree only changes during render response phase, which is exactly what we want to achieve this construct.
Here's a basic kickoff example:
#FacesComponent("com.example.Include")
public class Include extends UIComponentBase {
#Override
public String getFamily() {
return "com.example.Include";
}
#Override
public boolean getRendersChildren() {
return true;
}
#Override
public void encodeChildren(FacesContext context) throws IOException {
getChildren().clear();
((FaceletContext) context.getAttributes().get(FaceletContext.FACELET_CONTEXT_KEY)).includeFacelet(this, getSrc());
super.encodeChildren(context);
}
public String getSrc() {
return (String) getStateHelper().eval("src");
}
public void setSrc(String src) {
getStateHelper().put("src", src);
}
}
Which is registered in .taglib.xml as follows:
<tag>
<tag-name>include</tag-name>
<component>
<component-type>com.example.Include</component-type>
</component>
<attribute>
<name>src</name>
<required>true</required>
<type>java.lang.String</type>
</attribute>
</tag>
This works fine with the following view:
<h:outputScript name="fixViewState.js" />
<h:form>
<ui:repeat value="#{includeBean.includes}" var="include">
<h:commandButton value="Include #{include}" action="#{includeBean.setInclude(include)}">
<f:ajax render=":include" />
</h:commandButton>
</ui:repeat>
</h:form>
<h:panelGroup id="include">
<my:include src="#{includeBean.include}.xhtml" />
</h:panelGroup>
And the following backing bean:
#ManagedBean
#ViewScoped
public class IncludeBean implements Serializable {
private List<String> includes = Arrays.asList("include1", "include2", "include3");
private String include = includes.get(0);
private List<String> getIncludes() {
return includes;
}
public void setInclude(String include) {
return this.include = include;
}
public String getInclude() {
return include;
}
}
(this example expects include files include1.xhtml, include2.xhtml and include3.xhtml in the same base folder as the main file)
The fixViewState.js can be found in this answer: h:commandButton/h:commandLink does not work on first click, works only on second click. This script is mandatory in order to fix JSF issue 790 whereby the view state get lost when there are multiple ajax forms which update each other's parent.
Also note that this way each include file can have its own <h:form> when necessary, so you don't necessarily need to put it around the include.
This approach works fine in Mojarra, even with postback requests coming from forms inside the include, however it fails hard in MyFaces with the following exception during initial request already:
java.lang.NullPointerException
at org.apache.myfaces.view.facelets.impl.FaceletCompositionContextImpl.generateUniqueId(FaceletCompositionContextImpl.java:910)
at org.apache.myfaces.view.facelets.impl.DefaultFaceletContext.generateUniqueId(DefaultFaceletContext.java:321)
at org.apache.myfaces.view.facelets.compiler.UIInstructionHandler.apply(UIInstructionHandler.java:87)
at javax.faces.view.facelets.CompositeFaceletHandler.apply(CompositeFaceletHandler.java:49)
at org.apache.myfaces.view.facelets.tag.ui.CompositionHandler.apply(CompositionHandler.java:158)
at org.apache.myfaces.view.facelets.compiler.NamespaceHandler.apply(NamespaceHandler.java:57)
at org.apache.myfaces.view.facelets.compiler.EncodingHandler.apply(EncodingHandler.java:48)
at org.apache.myfaces.view.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:394)
at org.apache.myfaces.view.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:448)
at org.apache.myfaces.view.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:426)
at org.apache.myfaces.view.facelets.impl.DefaultFaceletContext.includeFacelet(DefaultFaceletContext.java:244)
at com.example.Include.encodeChildren(Include.java:54)
MyFaces basically releases the Facelet context during end of view build time, making it unavailable during view render time, resulting in NPEs because the internal state has several nulled-out properties. It's however possible to add individual components instead of a Facelet file during render time. I didn't really have had the time to investigate if this is my fault or MyFaces' fault. That's also why it didn't end up in OmniFaces yet.
If you're using Mojarra anyway, feel free to use it. I however strongly recommend to test it thoroughly with all possible use cases on the very same page. Mojarra has some state saving related quirks which might fail when using this construct.
I have a simple request scoped entity / pojo which has a Enum and a String as properties.
public Enum Type
{
None,
Email,
Fax;
}
#ManagedBean(name = "testEntity")
#RequestScoped
public class TestEntity
{
private Type type; //Default = None
private String address;
//getter and setter
}
This Enum has a field 'Email' which identifies a e-mail address with a related address.
In JSF I now want to enable/disable a validator of a address InputText field regarding the currently selected type in a SelectOneMenu.
<h:form id="formId">
<p:selectOneMenu id="type" value="#{testEntity.type}>
<p:ajax event="change" update=":formId:address"/>
<f:selectItem itemLabel="E-mail" itemValue="Email"/>
<f:selectItem itemLabel="Fax" itemValue="Fax"/>
</p:selectOneMenu>
<p:inputText id="address" value="#{testEntity.address}">
<f:validator validatorId="emailValidator" disabled="#{testEntity.type != 'Email'}"/>
</p:inputText>
<!-- button to call bean method with testEntity as param -->
</h:form>
It is not working the validator is never active but the ajax call is working since I can see the change value in other fields.
That's unfortunately not possible. The <f:xxx> tags are taghandlers (not UI components) which run during view build time, not during view render time. So if it's disabled during building of the view, it'll always be disabled until the view is recreated (e.g. by new request or a non-null navigation).
You'd need to have a "global" validator which delegates further to the desired validator based on the type attribute.
E.g.
<p:inputText ... validator="#{testEntity.validateAddress}" />
with
public void validateAddress(FacesContext context, UIComponent component, Object value) throws ValidatorException {
if (type == Email) {
context.getApplication().createValidator("emailValidator").validate(context, component, value);
}
}
Update OmniFaces has recently added a new <o:validator> tag which should solve exactly this problem as follows:
<o:validator validatorId="emailValidator" disabled="#{testEntity.type != 'Email'}"/>
See the showcase example here.
Maybe someone is interested in how I solved it thanks to BalusC help.
Pass type component clientId to custom converter.
<f:attribute name="typeComponentId" value=":formId:type"/>
Validator:
public class TestEntity implements Validator
{
#Override
public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException
{
final String typeComponentId = (String)component.getAttributes().get("typeComponentId");
final UIInput compType = (UIInput)context.getViewRoot().findComponent(typeComponentId);
if(compType != null)
{
final Type type = (Type)compType.getValue();
if(type == Type.Email)
new EmailValidator().validate(context, component, value);
}
}
}
Edit:
Not working inside a ui:repeat component such as p:datatable.