P: selectOneMenu does not create custom content via p: column in list <String>
I am trying to get Primefaces 5.2 selectOneMenu to display images along with their file names. This is what my xhtml looks like:
<h:form> <h:panelGrid id="createPanelGrid" columns="2"> <p:outputLabel value="Service Logo:" /> <p:selectOneMenu value="#{imageBean.selectedImage}" var="l"> <f:selectItem itemLabel="Select a logo" itemValue="" /> <f:selectItems value="#{imageBean.imageList}" var="logo" itemLabel="#{logo}" itemValue="#{logo}" /> <p:column> <p:graphicImage value="#{imageBean.imageFolder}/#{l}" style="max-width:50px;max-height:50px;" /> </p:column> <p:column>#{l}</p:column> </p:selectOneMenu> </h:panelGrid> ManagedBean (imageBean) has
public List<String> getImageList () { List<String> imageList = new ArrayList<String>(); File[] files = absoluteImageFolder.listFiles(); for (File file : files) { imageList.add(file.getName()); } return imageList; } and
private String selectedImage; public String getSelectedImage() { return selectedImage; } public void setSelectedImage(String selectedImage) { this.selectedImage = selectedImage; } However, the images do not appear on the web page, just the file names (I would post a screenshot, but I do not have enough reputation). I do not get two columns (first the image, then the file name), I just get the file name.
When I wrap the Strings file name in POJO and use the converter, it works, but it's just not the case with strings.
How can I make this work only with strings?
This inconvenient behavior is confirmed by the SelectOneMenuRenderer source code (line numbers correspond to 5.2):
260 if(itemValue instanceof String) { 261 writer.startElement("td", null); 262 writer.writeAttribute("colspan", columns.size(), null); 263 writer.writeText(selectItem.getLabel(), null); 264 writer.endElement("td"); 265 } 266 else { 267 for(Column column : columns) { 268 writer.startElement("td", null); 269 renderChildren(context, column); 270 writer.endElement("td"); 271 } 272 } So, if the value of an element is an instance of String , custom content via <p:column> completely ignored. It really makes no sense. The intuitive expectation is that user content is switched by the presence of the var attribute and / or <p:column> children. You better report problems to the PrimeFaces guys to explain / improve this.
The work around, in addition to providing String -typed item values, is to override SelectOneMenuRenderer with a custom renderer that wraps String in another object, which turned out to return exactly the same value in its toString() , like StringBuilder . Thus, the visualizer will be deceived that the values ββare not an instance of String . Glad they didn't check instanceof CharSequence .
public class YourSelectOneMenuRenderer extends SelectOneMenuRenderer { @Override protected void encodeOptionsAsTable(FacesContext context, SelectOneMenu menu, List<SelectItem> selectItems) throws IOException { List<SelectItem> wrappedSelectItems = new ArrayList<>(); for (SelectItem selectItem : selectItems) { Object value = selectItem.getValue(); if (value instanceof String) { value = new StringBuilder((String) value); } wrappedSelectItems.add(new SelectItem(value, selectItem.getLabel())); } super.encodeOptionsAsTable(context, menu, wrappedSelectItems); } } To run it, register it as shown below in faces-config.xml :
<render-kit> <renderer> <component-family>org.primefaces.component</component-family> <renderer-type>org.primefaces.component.SelectOneMenuRenderer</renderer-type> <renderer-class>com.example.YourSelectOneMenuRenderer</renderer-class> </renderer> </render-kit> The reason is that the Primefaces library is looking for a bean in the var attribute of the selectOneMenu component, but you are providing String objects that are not beans. Thus, the library does not display overlay on columns at all. You will need a bean (wrapper) in the var attribute and the corresponding converter in the converter attribute.