Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

p:selectOneMenu doesn't render custom content via p:column on List<String>

I'm trying to get a Primefaces 5.2 selectOneMenu to display images along with their filenames. This is what my xhtml currently 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>

The 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 are not rendered on the webpage, just the filenames (I'd post a screenshot but I don't have enough reputation). I don't get two columns (first the image, then the filename), I just get the filename itself.

When I wrap the filename Strings into a POJO and use a converter it works - but just with Strings it doesn't.

How can I get this to work with just Strings?

like image 898
Ginkobonsai Avatar asked Sep 23 '15 09:09

Ginkobonsai


1 Answers

This awkward behavior is confirmed by SelectOneMenuRenderer source code (line numbers match 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 item value is an instance of String, custom content via <p:column> is totally ignored. This does indeed not make any sense. The intuitive expectation is that the custom content is toggled by presence of var attribute and/or <p:column> children. You'd best report an issue to PrimeFaces guys to explain/improve this.

The work around, apart from providing non-String-typed item values, is to override the SelectOneMenuRenderer with a custom renderer which wraps the String in another object which happens to return exactly the same value in its toString(), such as StringBuilder. This way the renderer will be fooled that the values aren't an instance of String. Glad they didn't check for 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);
    }

}

In order to get it to run, register it as 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>
like image 187
BalusC Avatar answered Oct 14 '22 01:10

BalusC