myfaces-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From "K. Ghadami (JIRA)" <>
Subject [jira] Commented: (MYFACES-1493) h:selectOneMenu should resolve non-string objects in the value property without a converter
Date Sat, 21 Jun 2008 11:29:45 GMT


K. Ghadami commented on MYFACES-1493:

i analized and soleved the Problem.
The renderer trys to use the value in the select items to render the value of the option tags
- which is wrong!
Because the value of the selectitem is not need to be a string. Some people mean u just need
a converter but that is not what the jsf spec says.

So what we need is a renderer that simply use numbers for the value of the option tag. 
If like t share my render with u, that aceppt all all objets in the selectItem value.
it only testet for selectOneMenu. You can find the current code on my german blog:

in the facesconfig i registered my renderer:

<description>Renderer accepting objekts as value for select itmes</description>

Here the comes the render. it would be nice if some on could provide a real patch(svn diff)
so the pain will be gone in the next version of myfaces, ;-)
i changed, and mixed up so much code, that i am not quit sure how to provied a good patch:

package jsf;

import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import javax.faces.component.UIComponent;
import javax.faces.component.UISelectMany;
import javax.faces.component.UISelectOne;
import javax.faces.component.html.HtmlSelectManyMenu;
import javax.faces.component.html.HtmlSelectOneMenu;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;
import javax.faces.model.SelectItem;
import javax.faces.model.SelectItemGroup;

import org.apache.myfaces.shared_impl.component.EscapeCapable;
import org.apache.myfaces.shared_impl.renderkit.JSFAttr;
import org.apache.myfaces.shared_impl.renderkit.RendererUtils;
import org.apache.myfaces.shared_impl.renderkit.html.HTML;
import org.apache.myfaces.shared_impl.renderkit.html.HtmlRenderer;
import org.apache.myfaces.shared_impl.renderkit.html.HtmlRendererUtils;

 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.

/* 14.06.2008 modified by K.Ghadami
 * Now accepting non Sting values in selectItems 

 * X-CHECKED: tlddoc of h:selectManyListbox
 * @author Manfred Geiler (latest modification by $Author: matzew $)
 * @author Thomas Spiegl
 * @version $Revision: 597729 $ $Date: 2007-11-23 14:25:55 -0500 (Fri, 23 Nov
 *          2007) $
public class MenuRenderer extends HtmlRenderer {
	// private static final Log log = LogFactory.getLog(HtmlMenuRenderer.class);

	public void encodeEnd(FacesContext facesContext, UIComponent component)
			throws IOException {
		RendererUtils.checkParamValidity(facesContext, component, null);

		if (component instanceof UISelectMany) {
					(UISelectMany) component, isDisabled(facesContext,
		} else if (component instanceof UISelectOne) {

			renderMenu(facesContext, (UISelectOne) component, isDisabled(
					facesContext, component));
		} else {
			throw new IllegalArgumentException("Unsupported component class "
					+ component.getClass().getName());

	public void renderMenu(FacesContext facesContext, UISelectOne uiComponent,
			boolean disabled) throws IOException {

		ResponseWriter writer = facesContext.getResponseWriter();

		writer.startElement(HTML.SELECT_ELEM, uiComponent);
		HtmlRendererUtils.writeIdIfNecessary(writer, uiComponent, facesContext);
		writer.writeAttribute(HTML.NAME_ATTR, uiComponent
				.getClientId(facesContext), null);

		List<SelectItem> selectItemList;
		Converter converter;
		selectItemList = RendererUtils
				.getSelectItemList((UISelectOne) uiComponent);
		converter = HtmlRendererUtils.findUIOutputConverterFailSafe(
				facesContext, uiComponent);
		int size = 1;
		if (size == Integer.MIN_VALUE) {
			// No size given (Listbox) --> size is number of select items
			writer.writeAttribute(HTML.SIZE_ATTR, Integer
					.toString(selectItemList.size()), null);
		} else {
			writer.writeAttribute(HTML.SIZE_ATTR, Integer.toString(size), null);
		HtmlRendererUtils.renderHTMLAttributes(writer, uiComponent,
		if (disabled) {
			writer.writeAttribute(HTML.DISABLED_ATTR, Boolean.TRUE, null);

		if (HtmlRendererUtils.isReadOnly(uiComponent)) {
			writer.writeAttribute(HTML.READONLY_ATTR, HTML.READONLY_ATTR, null);

		UISelectOne uiSelectOne = (UISelectOne) uiComponent;

		/*The value was looked up during the validate phase. 
		 * Even if the hole list of selectitmes know cheched by a backing bean
		 * we have the origial selected object to compare. thats better than
		 * to check against the index, which would lead to
		 * strange results
		 * */
		Object lookupValue = uiSelectOne.getValue();

		renderSelectOptions(facesContext, uiComponent, converter,
				lookupValue, selectItemList, 0);
		// bug #970747: force separate end tag
		writer.writeText("", null);


	public void renderSelectOptions(FacesContext context,
			UIComponent component, Converter converter,
			Object lookupObject, List<SelectItem> selectItemList, Integer index)
			throws IOException {
		ResponseWriter writer = context.getResponseWriter();

		for (Iterator<SelectItem> it = selectItemList.iterator(); it.hasNext();) {
			SelectItem selectItem =;

			if (selectItem instanceof SelectItemGroup) {
				writer.startElement(HTML.OPTGROUP_ELEM, component);
				writer.writeAttribute(HTML.LABEL_ATTR, selectItem.getLabel(),
				SelectItem[] selectItems = ((SelectItemGroup) selectItem)
				renderSelectOptions(context, component, converter,
						lookupObject, Arrays.asList(selectItems), index);
			} else {

				writer.startElement(HTML.OPTION_ELEM, component);

				writer.writeAttribute(HTML.VALUE_ATTR, index.toString(), null);

				if (selectItem.getValue().equals(lookupObject)) {
							HTML.SELECTED_ATTR, null);


			boolean disabled = selectItem.isDisabled();
			if (disabled) {

			String labelClass = null;
			boolean componentDisabled = isTrue(component.getAttributes().get(

			if (componentDisabled || disabled) {
				labelClass = (String) component.getAttributes().get(
			} else {
				labelClass = (String) component.getAttributes().get(
			if (labelClass != null) {
				writer.writeAttribute("class", labelClass, "labelClass");

			boolean escape;
			if (component instanceof EscapeCapable) {
				escape = ((EscapeCapable) component).isEscape();
			} else {
				escape = RendererUtils.getBooleanAttribute(component,
						JSFAttr.ESCAPE_ATTR, true); // default is to escape

			if (escape || selectItem.isEscape()) {
				writer.writeText(selectItem.getLabel(), null);
			} else {


	private static boolean isTrue(Object obj) {
		if (!(obj instanceof Boolean))
			return false;

		return ((Boolean) obj).booleanValue();

	protected boolean isDisabled(FacesContext facesContext,
			UIComponent uiComponent) {
		// TODO: overwrite in extended HtmlMenuRenderer and check for
		// enabledOnUserRole
		if (uiComponent instanceof HtmlSelectManyMenu) {
			return ((HtmlSelectManyMenu) uiComponent).isDisabled();
		} else if (uiComponent instanceof HtmlSelectOneMenu) {
			return ((HtmlSelectOneMenu) uiComponent).isDisabled();
		} else {
			return org.apache.myfaces.shared_impl.renderkit.RendererUtils

	public void decode(FacesContext facesContext, UIComponent uiComponent) {
				.checkParamValidity(facesContext, uiComponent, null);

		if (uiComponent instanceof UISelectMany) {
			HtmlRendererUtils.decodeUISelectMany(facesContext, uiComponent);
		} else if (uiComponent instanceof UISelectOne) {
			HtmlRendererUtils.decodeUISelectOne(facesContext, uiComponent);
		} else {
			throw new IllegalArgumentException("Unsupported component class "
					+ uiComponent.getClass().getName());

	public Object getConvertedValue(FacesContext facesContext,
			UIComponent uiComponent, Object submittedValue)
			throws ConverterException {
				.checkParamValidity(facesContext, uiComponent, null);

		if (uiComponent instanceof UISelectMany) {
			return org.apache.myfaces.shared_impl.renderkit.RendererUtils
							(UISelectMany) uiComponent, submittedValue);
		} else if (uiComponent instanceof UISelectOne) {
			UISelectOne d = (UISelectOne) uiComponent;
			List<SelectItem> selectItemList = RendererUtils
			if (submittedValue instanceof String){
			Object x = lookup(facesContext, selectItemList.toArray(new SelectItem[0]), Integer
					.valueOf((String) submittedValue), 0);
			return x;
			} else {
				//return submittedValue;
				//nothing was selected in the menu, so we need to return null to pass the validation phase
				return null;

		} else {
			throw new IllegalArgumentException("Unsupported component class "
					+ uiComponent.getClass().getName());

	private Object lookup(FacesContext facesContext, SelectItem[] selectItemList,
			int submittedValue, Integer index) {

			for (SelectItem selectItem :selectItemList) {

			if (selectItem instanceof SelectItemGroup) {
				SelectItem[] selectItems = ((SelectItemGroup) selectItem)
				Object res = lookup(facesContext, selectItems, submittedValue,
				if (res != null)
					return res;

			} else {

				if (index.equals(submittedValue)) {
					return selectItem.getValue();
		return null;


> h:selectOneMenu should resolve non-string objects in the value property without a converter
> -------------------------------------------------------------------------------------------
>                 Key: MYFACES-1493
>                 URL:
>             Project: MyFaces Core
>          Issue Type: Improvement
>    Affects Versions: 1.1.4
>         Environment: JDK 1.5.0_08
>            Reporter: Paul Norrie
>            Priority: Minor
> h:selectOneMenu appears to require a converter if the object bound in the value field
is not a java.lang.String.
> To reproduce:
> JSP snippet:
>    <h:dataTable var="row" value="#{bean.rows}>
>       <h:column>
>          <h:selectOneMenu value="#{}"/>
>       <h:column>
>    </h:dataTable>
> Java snippet (backing bean):
>   private List<UserClass> rows;
>    public List getRows(){
>       return rows;
>    }
> Java snippet (UserClass):
>    static enum Day {MON, TUE, WED, THU, FRI, SAT, SUN};
>    private Day day;
>    public getDay(){
>       return day;
>    }
> Expected:
> the enum Day to be converted to a string and display either "MON", "TUE", etc...
> Actual:
> java.lang.IllegalArgumentException: Value is no String (class=UserClass$Day, value=MON)
>    at org.apache.myfaces.shared_impl.renderkit.RendererUtils.getConvertedStringValue(
>         at org.apache.myfaces.shared_impl.renderkit.html.HtmlRendererUtils.getSubmittedOrSelectedValuesAsSet(
>         at org.apache.myfaces.shared_impl.renderkit.html.HtmlRendererUtils.internalRenderSelect(
>         at org.apache.myfaces.shared_impl.renderkit.html.HtmlRendererUtils.renderMenu(
>         at org.apache.myfaces.shared_impl.renderkit.html.HtmlMenuRendererBase.encodeEnd(
>         at javax.faces.component.UIComponentBase.encodeEnd(
>         at org.apache.myfaces.shared_impl.renderkit.RendererUtils.renderChild(
>         at org.apache.myfaces.shared_impl.renderkit.RendererUtils.renderChildren(
>         at org.apache.myfaces.shared_impl.renderkit.RendererUtils.renderChild(
>         at org.apache.myfaces.shared_impl.renderkit.html.HtmlTableRendererBase.renderColumnBody(
>         at org.apache.myfaces.shared_impl.renderkit.html.HtmlTableRendererBase.encodeColumnChild(
>         at org.apache.myfaces.shared_impl.renderkit.html.HtmlTableRendererBase.encodeInnerHtml(
>         at org.apache.myfaces.shared_impl.renderkit.html.HtmlTableRendererBase.encodeChildren(
>         at javax.faces.component.UIComponentBase.encodeChildren(
> The RI and ADF Faces will quite happily work, however myfaces doc's seem to mean that
a convertor is needed.  
> See also 
> This is a pain - could it be fixed please?

This message is automatically generated by JIRA.
You can reply to this email to add a comment to the issue online.

View raw message