incubator-adffaces-user mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From "William Hoover" <whoo...@nemours.org>
Subject RE: Keeping selectOneChoice selection
Date Mon, 09 Apr 2007 16:17:42 GMT
Francisco,

I have found that there is a fundamental problem with how UINamingContainers are processed
when row indexes are inserted into the ids of EditableValueHolders within the container rows.


Understanding The Problem:
As you may already know, tables have only columns referenced in the page so it can handle
an almost infinite number of rows. Well, because you have only specified an id for a component
within the table column the component inserts a row index reference in the holders id (via
getClientRowKey() in a table) for each row. This ensures that each iteration of that row has
a unique id. The problem with this is that in some cases this breaks automatic setting of
bean values (if they are value bound to an EditableValueHolder within a row). Another issue
is that "selectOneChoice" components in particular do not have a mechanism in place to ensure
that a submitted value that gets set on the component is one of the values within the internal
"SelectItemList" so virtually any value can get set on it. If the submitted value is not in
the list it adds a blank entry in the drop down menu- what? I have also noticed that selectOneChoice
component does not handle value conversion like regular input text components do. Semi-primitive
values such as Integers, Booleans, etc. are not converted automatically like they are for
regular input text components. I had some of the same issues with the "detailStamp" so I created
an extended table component that allows only one toggled "detailStamp" at a time and overrides
the "getClientRowKey()" excluding row indexes for components within the "detailStamp". This
solution works very well, but still does not address EditableValueHolders within the rows
themselves. It seems to me that we need to reevaluate the usage of inserting row indexes into
ids!

Possible Solutions:
The first thing I would check is that you have the valuePassThru="true" to ensure that its
not trying to use the index as the passed value (not sure why anyone would want to be restricted
to just an index). If want to ensure that your value is never set to a value that does not
exist in the options you can use a value change listener:


	/**
	 * Verifies that an editable value holder event components value is a valid
	 * option. If it is not it sets it back to the old value.
	 * 
	 * @see #getEditableValueHolderValue(EditableValueHolder)
	 * @param event
	 */
	public final void verifyValueOption(ValueChangeEvent event) {
		try {
			if (event.getComponent() instanceof EditableValueHolder) {
				EditableValueHolder valueHolder = (EditableValueHolder) event
						.getComponent();
				if (!isValidSelectOption(event.getComponent(), event.getNewValue())) {
					// invalid option- regress to old value
					valueHolder.setValue(event.getOldValue());
					valueHolder.setSubmittedValue(event.getOldValue());
				}
			}
		} catch (Throwable e) {
			log.warn("Unable to verify select value. " + e.getMessage()
					+ " cause: " + e.getCause());
		}
	}

	/**
	 * Determines if the specified value is a valid selection option in the
	 * specified select component.
	 * 
	 * @param component
	 * @param value
	 * @return is the specified value a valid select option
	 */
	public static final boolean isValidSelectOption(UIComponent component,
			Object value) {
		try {
			List<SelectItem> items = SelectItemSupport.getSelectItems(
					component, SelectItemSupport.getConverter(component));
			for (SelectItem item : items) {
				if (item.getValue() != null && item.getValue().equals(value)) {
					return true;
				}
			}
		} catch (Exception e) {
			_LOG.warning("Unable to determine if the specified value: " + value
					+ " is valid for the select component: " + component);
		}
		return false;
	}



Probably not the most elegant solution, but nonetheless an effective one is to manage the
submitted row index problems yourself. If nothing else this will provide you with a debugging
tool to determine if the row index references are preventing your values from getting set:
	/**
	 * <p>
	 * Gets a parameter value from the request scope by an editale value holder
	 * components id. This method will set/return the holders submitted value
	 * from the request.
	 * </p>
	 * <p>
	 * The holders client id is checked against a possible matching id request
	 * parameter. The client id of the holder and the request parameter matching
	 * the hodler id may have their own row indexing (such is the case with a
	 * select menu).
	 * <ol>
	 * <li>The client id against the request parameter</li>
	 * <li>The client id w/o row index against the request parameter</li>
	 * <li>The client id against the request parameter w/o row index</li>
	 * </ol>
	 * </p>
	 * 
	 * @param context
	 * @param holder
	 * @return the parameter value
	 */
	@SuppressWarnings("unchecked")
	public static final Object getRequestParameterByHolderId(
			FacesContext context, EditableValueHolder holder) {
		if (log.isDebugEnabled())
			log.debug("getRequestParameterForNamingContainer(" + context + ','
					+ holder + ")");
		if (holder != null && holder instanceof UIComponent) {
			String clientId = ((UIComponent) holder).getClientId(context);
			String clientIdWithOutRowIds = removeRowIdReferences(clientId);
			String id = ((UIComponent) holder).getId();
			Map<String, Object> map = context.getExternalContext()
					.getRequestParameterMap();
			if (map != null && map.entrySet() != null) {
				for (Map.Entry<String, Object> entry : map.entrySet()) {
					if (entry.getKey().indexOf(id) >= 0) {
						if (entry.getKey().equalsIgnoreCase(clientId)
								|| entry.getKey().equalsIgnoreCase(
										clientIdWithOutRowIds)
								|| removeRowIdReferences(
										entry.getKey()).equalsIgnoreCase(
										clientId)) {
							try {
								setConvertedAndValidatedValue(context,
												holder, entry.getValue());
							} catch (Exception e) {
								log.error("Unable to capture the converted "
										+ "value for component(" + holder
										+ ") value: " + entry.getValue(), e);
							}
							return holder.getSubmittedValue();
						}
					}
				}
			}
		}
		return null;
	}

	/**
	 * Removes any row id references that may exist within the specified client
	 * id
	 * 
	 * @param clientId
	 * @return the client id
	 */
	public static final String removeRowIdReferences(String clientId) {
		if (log.isDebugEnabled())
			log.debug("removeRowIdReferences(" + clientId + ')');
		if (clientId != null) {
			String[] ids = clientId.split(String
					.valueOf(NamingContainer.SEPARATOR_CHAR));
			for (String id : ids) {
				try {
					Integer.parseInt(id);
				} catch (Exception e) {
					continue;
				}
				clientId = clientId.replace(NamingContainer.SEPARATOR_CHAR + id
						+ NamingContainer.SEPARATOR_CHAR, String
						.valueOf(NamingContainer.SEPARATOR_CHAR));
			}
		}
		return clientId;
	}

	/**
	 * Invokes the internal converter/validators(s) for an editable value holder
	 * and returns the converted value
	 * 
	 * @param context
	 * @param holder
	 * @param value
	 * @return the converted value
	 * @throws ValidatorException
	 * @throws EvaluationException
	 * @throws MethodNotFoundException
	 */
	public static final Object setConvertedAndValidatedValue(FacesContext context,
			EditableValueHolder holder, Object value)
			throws ValidatorException, EvaluationException,
			MethodNotFoundException {
		if (holder != null) {
			if (holder.getConverter() != null) {
				value = holder.getConverter().getAsObject(context,
						(UIComponent) holder,
						value != null ? value.toString() : null);
			}
			if (holder instanceof UIComponent) {
				if (holder.getValidators() != null) {
					for (Validator v : holder.getValidators()) {
						v.validate(context, (UIComponent) holder, value);
					}
				}
			}
			if (holder.getValidator() != null) {
				holder.getValidator().invoke(context, new Object[] { value });
			}
			holder.setSubmittedValue(value);
			return value;
		}
		return null;
	}

-----Original Message-----
From: Francisco Passos [mailto:francisco.passos@opensoft.pt]
Sent: Monday, April 09, 2007 11:17 AM
To: William Hoover
Subject: RE: Keeping selectOneChoice selection


No, it is in a facet for a panelPage.

In it, I have a panelGroupLayout, then a panelHorizontalLayout and finally
in it the panelPage.

If you feel it's best, I can attach the code or paste it in the mail body.

Either way, if the selectOneChoice were in a table (something I will
surely need), what would the solution be?

Thank you,

Francisco



> Francisco,
>
> Is your selectOneChoice in a table?
>
> -----Original Message-----
> From: Francisco Passos [mailto:francisco.passos@opensoft.pt]
> Sent: Monday, April 09, 2007 10:55 AM
> To: adffaces-user@incubator.apache.org
> Subject: Keeping selectOneChoice selection
>
>
> Hello there.
>
> I'm new to JSF and Trinidad, so please bear with my simplistic doubts.
>
> I'm struggling to keep a selectOneChoice selection upon a postback using a
> request-scoped bean.
>
> At first I couldn't even maintain the values in the list, but I found that
> placing a h:inputHidden on the page and declaring its value to be
> #{myBean.valueList}, they could be kept. Furthermore I've tested the seme
> using pageFlow and it worked.
>
> However, I cannot keep the selected value in the dropdown list, it just
> resets.
>
> What is the correct way to do this simple task without using session
> beans?
>
> Thank you for your time and attention,
>
> Francisco Passos
>
>
>


Francisco Passos
Opensoft - Soluções Informáticas, Lda
Telemóvel:  +351 91 238 52 76
Escritório: +351 21 380 44 10
Email:      francisco.passos@opensoft.pt



Mime
View raw message