On an Xpage I have a show/hide function for an xp:inputtext control. when the inputbox is shown I want it to be mandatory.
Here is my code:
<xp:panel
styleClass="row"
id="pnlInterest">
<div class="col-sm-12">
<table>
<tr>
<td>
<xp:checkBox id="cbInterest" value="#{eventBean.event.interest}">
<xp:eventHandler event="onclick"
submit="true" refreshMode="partial"
refreshId="pnlInterest">
<xp:this.action><![CDATA[#{javascript:print("click")
print("selected " + eventBean.getEvent().getInterest())
if(eventBean.getEvent().getInterest() != "true"){
eventBean.getEvent().setYourName("");
}}]]></xp:this.action>
</xp:eventHandler></xp:checkBox>
</td>
<td>
<label class="checkbox">
<span>Are you interested in this event?
</label>
</td>
</tr>
<tr>
<td></td>
<td>
<xp:panel>
<xp:this.rendered><![CDATA[#{javascript:if ("true" == eventBean.getEvent().getInterest()){
return true;
} else{
return false;
}}]]></xp:this.rendered>
<div class="form-inline">
<div class="form-group">
<xp:inputText
id="inpYourName"
value="#{eventBean.event.yourName}" required="true">
<xp:this.attrs>
<xp:attr
name="placeholder" value="Enter your name" />
</xp:this.attrs>
<xp:this.validators>
<xp:validateRequired
message="You forgot to enter your name">
</xp:validateRequired>
</xp:this.validators></xp:inputText>
<xp:message id="msg"
for="inpYourName">
</xp:message>
</div>
</div>
</xp:panel>
</td>
</tr>
</table>
</div>
</xp:panel>
The show / hide works fine when I do not have a validation set for the xp:inputtext. But as soon as I add it and I show it via the checkbox I can not hide it any more. The event in the onclick is not being triggered anymore.
How can I make this idea work? (Show via checkbox inputtext control, make it mandatory, Hide it again via checkbox and make it not mandatory)
Let's start by saying that you're tripped by what happens in the JSF lifecycle and when.
With the following event handler, without any execMode="partial"
:
<xp:eventHandler event="onclick"
submit="true" refreshMode="partial"
refreshId="pnlInterest">
you are telling the server to "re-execute" the whole page (this isn't a good practice, you're not being efficient about what portions of the page the server could actually skip because they don't play any active part, and this is a typical example), included the xp:inputText required="true"
. The validation phase fails because the input is empty - it doesn't know that you don't need it anymore - and therefore skips any other subsequent phase, which includes hiding the input again.
You could fix it by adding the disableValidators="true"
property in the eventHandler. Basically this instructs the server to skip the validation phase which would yield the intended result for you. Actually I'm rarely leaning toward this option because you simply go around it by narrowing the scope of the execution. For example, I would refactor the code to make it more efficient but also follow the standards (in fact you are also clearing the input when you clear the checkbox, in such cases it would be better to use a valueChangeListener
). It could go along these lines:
public class Controller implements Serializable {
private static final long serialVersionUID = 1L;
private String interest;
private String yourName;
public String getInterest() {
return interest;
}
public String getYourName() {
return yourName;
}
public boolean isInterested() {
return interest != null && Boolean.valueOf(interest);
}
public void setInterest(String interest) {
this.interest = interest;
}
public void setYourName(String yourName) {
this.yourName = yourName;
}
public void onInterestChange(ValueChangeEvent event) throws AbortProcessingException {
if ("true".equals(event.getNewValue())) {
setYourName(null);
}
}
}
The onInterestChange
will take care of clearing the input name value any time interest
will be set to something different from boolean true
.
The isInterested
method is there for convenience and conciseness on the xsp but all this hustle could be avoided if you created a boolean converter.
<table>
<tr>
<td>
<xp:checkBox id="cbInterest" value="#{controller.interest}"
checkedValue="true" uncheckedValue="false" valueChangeListener="#{controller.onInterestChange}">
<xp:eventHandler event="onchange" submit="true" execMode="partial" refreshMode="partial"
refreshId="rowName" />
</xp:checkBox>
</td>
<td>
<label class="checkbox">
<span>Are you interested in this event?</span>
</label>
</td>
</tr>
<xp:tr id="rowName">
<td />
<td>
<xp:div styleClass="form-inline" rendered="#{controller.interested}">
<div class="form-group">
<xp:inputText id="inpYourName" value="#{controller.yourName}"
required="true">
<xp:this.attrs>
<xp:attr name="placeholder" value="Enter your name" />
</xp:this.attrs>
<xp:this.validators>
<xp:validateRequired message="You forgot to enter your name" />
</xp:this.validators>
</xp:inputText>
<xp:message id="msg" for="inpYourName" />
</div>
</xp:div>
</td>
</xp:tr>
</table>
<xp:button value="Submit" id="button1">
<xp:eventHandler event="onclick" submit="true"
refreshMode="complete" />
</xp:button>
As you can see I didn't make use of disableValidators="true"
in the checkbox component. That's because I narrowed the execution down to the very checkbox itself. When the form is submitted actually the server will execute only on the checkbox, thus ignoring everything else that lives on the view root (that means no xp:inputText required="true"
to disturb).