Validating required on composite component with multiple inputs - jsf-2

I am using a composite component that uses a dropdown and input. The required attribute is set on the dropdown but if 'Other' is selected, then the input should also be required. I guess the required field is being validated on the 'cc.attr.value', but I also need to validate 'cc.attr.otherValue'. So, I am using the postValidate to check this required value. Clicking save should validate the inputs inside the component and then call the postValidate action. However, after postValidate, it is still calling the setter in the update models phase.
<composite:interface componentType="myInputComponent">
<composite:attribute name="value" required="true" type="java.lang.String" />
<composite:attribute name="otherValue" required="true" type="java.lang.String" />
</composite:interface>
<composite:implementation>
<s:validateAll>
<s:span id="input">
<h:selectOneMenu id="selectInput" value="#{cc.attrs.value}"
required="#{cc.required}"
onchange="otherInput = #{rich:element('otherInput')}; otherInput.value = ''; if (this.options[this.selectedIndex].text == 'Other') { otherInput.style.display = ''; otherInput.required = #{cc.required}; } else { otherInput.style.display = 'none'; otherInput.required = false; }">
<f:event type="postValidate" listener="#{cc.postValidate}" />
<!-- validators here -->
</h:selectOneMenu>
<s:span id="other">
<h:inputText id="otherInput" value="#{cc.attrs.otherValue}"
size="#{cc.attrs.width}" style="#{showOther ? '' : 'display: none'}"
onkeydown="#{not cc.attrs.inplace ? 'ignoreEnter(event)' : ''}"
onkeypress="#{cc.attrs.inplace ? 'saveOnEnter(event)' : ''}">
<f:event type="postValidate" listener="#{cc.postValidate}" />
</h:inputText>
</s:span>
</s:span>
<a4j:commandButton action="#{cc.attrs.saveAction}"
value="Save"
execute="input"
render="input" />
</s:validateAll>
</composite:implementation>
Here is the UIComponent
#FacesComponent("myInputComponent")
public class MyInputComponent extends UIInput implements NamingContainer {
static final String OTHER_INPUT_ID = "otherInput";
static final String OTHER_LABEL = "Other";
public void postValidate(ComponentSystemEvent event) {
UIInput input = (UIInput) event.getComponent();
if (input != null && OTHER_INPUT_ID.equals(input.getId())) {
UIInput selectInput = (UIInput) event.getComponent().findComponent("selectInput");;
boolean useOtherValue = false;
if (selectInput != null) {
Object value = selectInput.getValue();
useOtherValue = isUseOtherValue(value);
if (useOtherValue && isRequired()) {
String otherValue = (String) input.getValue();
if (StringUtils.emptyString(otherValue)) {
setInvalid(input);
showRequiredMessage(input);
}
}
}
}
}
private void setInvalid(UIInput input) {
input.setValid(false);
FacesContext.getCurrentInstance().validationFailed();
}
}
The component being used:
<my:myInput value="#{bean.value}" otherValue="#{bean.otherValue}" />
The setter on bean.value is still called after postValidate fails.
I've tried this on a similar component with only one input, and it works as expected when required value fails. It does not go into the setter.

Related

JSF 2 FacesConverter doesn't work with composite component

I've got simple composite component which has to render h:selectManyListbox with h:messages associated with it. The problem is with using faces converter class. It seems to be not working with it (only if used in component code).
Composite component code :
<?xml version="1.0" encoding="UTF-8"?>
<html 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:composite="http://java.sun.com/jsf/composite">
<!-- INTERFACE -->
<composite:interface>
<composite:attribute name="description" required="true" />
<composite:attribute name="fieldValue" required="true" />
<composite:attribute name="idc" required="true" />
<composite:attribute name="size" required="true" />
</composite:interface>
<!-- IMPLEMENTATION -->
<composite:implementation>
<tr>
<td nowrap="nowrap">
<h:outputText value="#{cc.attrs.description}" />
</td>
<td>
<h:selectManyListbox
id="#{cc.attrs.idc}"
size="#{cc.attrs.size}"
value="#{cc.attrs.fieldValue}">
<composite:insertChildren />
</h:selectManyListbox>
</td>
<td>
<h:message for="#{cc.attrs.idc}" />
</td>
</tr>
</composite:implementation>
</html>
When I use it on sample.xhtml page (as shown below), I get 'Validation Error: Value is not valid'.
.....
But when on the same page I put code like:
<tr>
<td><h:outputText value="Plugins" /></td>
<td>
<h:selectManyListbox
id="plugins"
value="#{bean.currentPlugins}"
size="6">
<f:selectItems value="#{bean.availablePlugins}" />
</h:selectManyListbox>
</td>
<td><h:message for="plugins" /></td>
</tr>
everything goes fine.
The managed bean 'bean' class is
#ManagedBean
#SessionScoped
public class Bean extends GenericManagedBean
implements Serializable {
ElementClass[] currentPlugins;
// getter & setter for currentPlugins
// ...
public List<ElementClass> getAvailablePlugins() {
// .. some code
return list;
}
}
and ElementClass is
public class ElementClass extends GenericEntity implements Serializable {
private static final long serialVersionUID = 9159873495276902436L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "ID")
private Integer id;
// other fields
// ...
// getters & setters
// ...
#Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
if (!(object instanceof ElementClass)) {
return false;
}
ElementClass other = (ElementClass) object;
if ((this.id == null && other.id != null)
|| (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}
}
For the ElementClass I've got converter class
#FacesConverter(forClass = ElementClass.class)
public class ElementClassConverter implements Converter {
#Override
public Object getAsObject(FacesContext fc, UIComponent uiComponent,
String elemId) {
if (!StringUtils.isEmpty(elemId)) {
// this code gets ElementClass object entity from database
ApplicationBean applicationBean = JSFHelper
.findBean("applicationBean");
return applicationBean.getService()
.findElementClassById(
Integer.parseInt(elemId));
} else {
return null;
}
}
#Override
public String getAsString(FacesContext fc, UIComponent uiComponent,
Object elem) {
return elem != null ? ((ElementClass ) elem).getId()
.toString() : "";
}
}
And standard question: what am I doing wrong? I assume this is my lack of knowledge rather than jsf implemenation bug. I use JBoss 7.1.1 AS and everything, what is bundled with it + RichFaces 4.

resetValue in custom UIInput Composite Component not working

I have a custom UIInput wrapped in a composite component like this:
<cc:interface componentType="SingleUpload">
<cc:attribute name="value" required="true" />
...
</cc:interface>
<cc:implementation>
...
<p:fileUpload id="fileUpload" update="#form" auto="true"
fileUploadListener="#{cc.fileUploaded}" rendered="#{cc.attrs.value == null}"/>
...
<h:commandButton rendered="#{cc.attrs.value != null}" action="#{cc.removeFile}">
<p:ajax execute="#this" update="#form" />
</h:commandButton>
...
</cc:implementation>
The backing component looks like this:
#FacesComponent(value = "SingleUpload")
public class SingleUpload extends UIInput implements NamingContainer, Serializable {
/** */
private static final long serialVersionUID = 2656683544308862007L;
#Override
public String getFamily() {
return "javax.faces.NamingContainer";
}
public void fileUploaded(FileUploadEvent event) throws IOException {
FileData file = new FileData();
file.setContentMimeType(MimeTypeUtils.getContentType(event.getFile().getContentType(), event.getFile().getFileName()));
file.setInputStream(event.getFile().getInputstream());
file.setName(FilenameUtils.getName(event.getFile().getFileName()));
setValue(file);
}
public void removeFile() {
resetValue();
// value should be null now, but it is not, why??
FileData value=(FileData) getValue();
}
}
It is used this way:
<ki:singleUpload value="#{fileModel.file}" title="File Upload" />
So when the action removeFile is called, i want to set the value to null. This works, when I follow these steps:
load a page containing a singleUpload component with no initial value (fileModel.file == null)
upload a file, so value in SingleUpload is not null anymore
remove the file
But when I do the following
load a page containing a singleUpload component with an initial value (fileModel.file != null)
remove the intial value (click on button, removeFile is called)
=> removing the value from the component does not seem to be possible. Why??

How to pass attributes to a composite-component

I am having trouble in using a JSF composite-component in the right way. I put some components together and everything was working. Then I just extracted the code to a composite-component, and passed the corresponding attributes and suddenly I am getting conversation exceptions.
<composite:interface>
<composite:attribute name="selectedIDs" type="java.util.Collection" required="true"/>
<composite:attribute name="selectItems" type="java.util.List" required="true" />
<composite:attribute name="addAction" required="true"/>
<composite:attribute name="deleteAction" required="true"/>
<composite:attribute name="deleteButtonDisabled" />
<composite:attribute name="ajaxListener" method-signature="void listener(javax.faces.event.AjaxBehaviorEvent)"/>
</composite:interface>
<composite:implementation>
<div class="myClass">
<h:outputStylesheet library="views" name="selectManyControlPanel.css" target="head" />
<h:form>
<h:selectManyListbox value="#{cc.attrs.selectedIDs}">
<f:selectItems value="#{cc.attrs.selectItems}" />
<f:ajax render="delete"
listener="#{cc.attrs.ajaxListener}" />
</h:selectManyListbox>
<br />
<h:commandButton id="delete" value="Delete"
disabled="#{cc.attrs.deleteButtonDisabled}"
action="#{cc.attrs.deleteAction}" />
<h:commandButton id="add" value="Add" action="#{cc.attrs.addAction}"/>
</h:form>
</div>
</composite:implementation>
Here is the page where I am using the newly created component
<view:selectManyControlPanel
selectedIDs="#{bean.selectedIds}"
selectItems="#{bean.listOfSelectItems}"
addAction="#{bean.addNew}"
deleteAction="#{bean.deleteSelection}"
ajaxListener="#{bean.selectionChanged}"
deleteButtonDisabled="#{bean.deleteButtonDisabled}" />
Bean (some methods skipped an parts renamed)
package views;
#SuppressWarnings("serial")
#Named
#RequestScoped
public class Bean implements Serializable, IOverviewView {
#Inject
Presenter presenter;
private boolean deleteButtonDisabled;
private List<SelectItem> listOfSelectItems;
private Set<Long> selectedIds;
public Bean(){
deleteButtonDisabled = true;
listOfSelectItems = new ArrayList<SelectItem>(10);
}
public void selectionChanged(AjaxBehaviorEvent event){
if(selectedIds.isEmpty())
deleteButtonDisabled = true;
else
deleteButtonDisabled = false;
}
public void deleteBikes(){
presenter.delete(selectedIds);
}
public void addNew(){
presenter.addNew();
}
public List<SelectItem> getListOfSelectItems() {
return listOfSelectItems;
}
public Set<Long> getSelectedIds() {
return selectedIds;
}
#PostConstruct
public void init(){
System.out.println("INITIALIZING BEAN: " + this.getClass().getName());
this.presenter.setView(this);
this.presenter.init();
}
public boolean isDeleteButtonDisabled() {
return deleteButtonDisabled;
}
#Override
public void setDeleteButtonEnabled(boolean isEnabled) {
deleteButtonDisabled = !isEnabled;
}
public void setListOfSelectItems(List<SelectItem> list) {
this.listOfSelectItems = list;
}
public void setSelectedIds(Set<Long> selectedIds) {
this.selectedIds = selectedIds;
}
#Override
public void updateBikesList(Set<ViewObject> objectsToDisplay) {
updateList(objectsToDisplay);
}
private void updateList(Set<ViewObject> objectsToDisplay){
listOfSelectItems.clear();
for (ViewObject vO : objectsToDisplay) {
final String label = vO.getManufacturer() + ", " + vO.getModel() + " (" + vO.getYear() + ")";
listOfSelectItems.add(new SelectItem(vO.getId(), label));
}
}
....
}
Exception
javax.el.ELException: /resources/views/selectManyControlPanel.xhtml #25,56 value="#{cc.attrs.selectedIDs}": /index.xhtml #21,70 selectedIDs="#{bean.selectedIds}": Cannot convert [Ljava.lang.String;#1e92093 of type class [Ljava.lang.String; to interface java.util.Set
The only thing that changed is that I am using the composition instead of the plain code. The EL-expressions are still the same. Can someone enlighten me please? I expected that the parameters are just passed through but obviously not...
This is related to Mojarra issue 2047. It's scheduled to be fixed in the upcoming 2.2.
The issue ticket also proposes the following workaround:
<view:selectManyControlPanel
selectedIDsBean="#{bean}"
selectedIDsProperty="selectedIds"
with in CC interface
<composite:attribute name="selectedIDsBean" required="true"/>
<composite:attribute name="selectedIDsProperty" required="true"/>
and in CC implementation
<h:selectManyListbox value="#{cc.attrs.selectedIDsBean[cc.attrs.selectedIDsProperty]}">

Checkbox inside ui:repeat not refreshed by Ajax

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.

How to highlight a primefaces tree node from backing bean

I am working with primefaces tree component. There is a context menu for the tree (add a node, edit node, delete node). After performing some operation, I need to refresh the tree and then highlight the node added or edited.
This is my code.
index.xhtml
<p:treeNode>
<h:outputText value="#{node}" />
</p:treeNode>
</p:tree>
<p:contextMenu for="pTree" id="cmenu">
<p:menuitem value="Add topic as child" update="pTree, cmenu"
actionListener="#{treeBean.addChildNode}" />
<p:menuitem value="Add topic Below" update="pTree, cmenu"
actionListener="#{treeBean.addTopicBelow}" />
<p:menuitem value="Delete Topic" update="pTree, cmenu"
actionListener="#{treeBean.deleteNode}" />
</p:contextMenu>
treeBean.java
public class TreeBean implements Serializable {
private TreeNode root;
public TreeBean() {
root = new DefaultTreeNode("Root", null);
// GET the root nodes first L0
List<TracPojo> rootNodes = SearchDao.getRootNodes111();
Iterator it = rootNodes.iterator();
while (it.hasNext()) {
TracPojo t1 = (TracPojo) it.next();
String tid = t1.getTopicID();
TreeNode node1 = new DefaultTreeNode(t1, root);
}
}
public TreeNode getRoot() {
return root;
}
public void addChildNode(ActionEvent actionEvent)
{
List record = NewSearchDao.getRecord(selectedNode);
Iterator it = record.iterator();
while (it.hasNext()) {
Object[] record1 = (Object[]) it.next();
setParentID_dlg((String) record1[0]);
setSortIndex((Integer) record1[2]);
}
}
public void saveChilddNode() {
System.out.println("Save as Child Node ........");
}
}
Primefaces p:treeNode has an attribute styleClass. You could set this dynamically from your backing bean. The view would look like:
<p:tree>
<p:treeNode styleClass="#{treeBean.styleClass}">
<h:outputText value="#{node}" />
</p:treeNode>
</p:tree>
Then add a member styleClass to your TreeBean with get/set method that returns a string representing the style class:
public class TreeBean implements Serializable {
private String styleClass;
...
public String getStyleClass() {
// your style selection logic here
}
...
}
Don't forget to add the style classes to your css.
Unless you set the selectedNode, which you declare as selection="#{treeBean.selectedNode}", to null, it is already selected and the only thing you have to do is to update the tree component from the triggering component; in your case it is:
<p:menuitem update=":yourForm:pTree" /*rest of the stuff*/ />

Resources