com.sun.faces.renderkit.html_basic.RadioRenderer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jakarta.faces Show documentation
Show all versions of jakarta.faces Show documentation
EE4J Compatible Implementation for Jakarta Faces API
/*
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
// RadioRenderer.java
package com.sun.faces.renderkit.html_basic;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import javax.el.ELException;
import javax.el.ValueExpression;
import javax.faces.component.UIComponent;
import javax.faces.component.UINamingContainer;
import javax.faces.component.UISelectItem;
import javax.faces.component.UISelectOne;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.convert.Converter;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ComponentSystemEvent;
import javax.faces.event.ComponentSystemEventListener;
import javax.faces.event.ListenerFor;
import javax.faces.event.PostAddToViewEvent;
import javax.faces.model.SelectItem;
import com.sun.faces.RIConstants;
import com.sun.faces.renderkit.Attribute;
import com.sun.faces.renderkit.AttributeManager;
import com.sun.faces.renderkit.RenderKitUtils;
import com.sun.faces.renderkit.SelectItemsIterator;
import com.sun.faces.util.RequestStateManager;
import com.sun.faces.util.Util;
/**
* ReadoRenderer is a class that renders the current value of
* UISelectOne or UISelectMany component as a list of
* radio buttons
*/
@ListenerFor(systemEventClass=PostAddToViewEvent.class)
public class RadioRenderer extends SelectManyCheckboxListRenderer implements ComponentSystemEventListener {
private static final Attribute[] ATTRIBUTES =
AttributeManager.getAttributes(AttributeManager.Key.SELECTONERADIO);
// -------------------------------------------------------------------------------------------------- Public Methods
/**
* After adding component to view, if component has group attribute set, then pre-collect the components by group.
*/
@Override
public void processEvent(ComponentSystemEvent event) throws AbortProcessingException {
UISelectOne radio = (UISelectOne) event.getComponent();
Group group = getGroup(event.getFacesContext(), radio);
if (group != null) {
group.addRadio(event.getFacesContext(), radio);
}
}
/**
* This override delegates to {@link #decodeGroup(FacesContext, UISelectOne, String)}
* when 'group' attribute is set. It will only decode when the current component is the first one of group.
*/
@Override
public void decode(FacesContext context, UIComponent component) {
UISelectOne radio = (UISelectOne) component;
Group group = getGroup(context, radio);
if (group != null) {
decodeGroup(context, radio, group);
}
else {
super.decode(context, component); // Continue default decoding.
}
}
/**
* This override delegates to {@link #encodeEndGroup(FacesContext, UISelectOne, String)}
* when 'group' attribute is set.
*/
@Override
public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
UISelectOne radio = (UISelectOne) component;
Group group = getGroup(context, radio);
if (group != null) {
encodeEndGroup(context, radio, group);
}
else {
super.encodeEnd(context, component); // Continue default table rendering.
}
}
// ------------------------------------------------------- Protected Methods
/**
* The difference with default decoding is:
* Submitted value is obtained by group name.
* Submitted value is prefixed with client ID of radio button component, this need to be compared and trimmed.
* If any submitted value does not belong to current radio button component, reset its value.
*/
protected void decodeGroup(FacesContext context, UISelectOne radio, Group group) {
rendererParamsNotNull(context, radio);
if (!shouldDecode(radio)) {
return;
}
String clientId = decodeBehaviors(context, radio);
if (clientId == null) {
clientId = radio.getClientId(context);
}
assert(clientId != null);
Map requestParameterMap = context.getExternalContext().getRequestParameterMap();
String newValue = requestParameterMap.get(group.getClientName());
String prefix = clientId + UINamingContainer.getSeparatorChar(context);
if (newValue != null) {
if (newValue.startsWith(prefix)) {
String submittedValue = newValue.substring(prefix.length());
setSubmittedValue(radio, submittedValue);
if (logger.isLoggable(Level.FINE)) {
logger.fine("submitted value for UISelectOne group component "
+
radio.getId()
+ " after decoding "
+ submittedValue);
}
} else {
radio.resetValue();
}
} else {
// There is no submitted value at all, but this is different from a null value.
radio.setSubmittedValue(RIConstants.NO_VALUE);
}
}
/**
* The difference with default encoding is:
* Every radio button of same 'group' will have same 'name' attribute rendered, relative to UIForm parent.
* The 'value' attribute of every radio button is prefixed with client ID of radio button component itself.
* No additional (table) markup is being rendered.
* Label, if any, is rendered directly after radio button element, without additional markup.
*/
protected void encodeEndGroup(FacesContext context, UISelectOne radio, Group group)
throws IOException
{
rendererParamsNotNull(context, radio);
if (!shouldEncode(radio)) {
return;
}
SelectItem currentItem = RenderKitUtils.getSelectItems(context, radio).next();
String clientId = radio.getClientId(context);
Object itemValue = currentItem.getValue();
Converter converter = radio.getConverter();
boolean checked = isChecked(context, radio, itemValue);
boolean disabled = Util.componentIsDisabled(radio);
ResponseWriter writer = context.getResponseWriter();
assert (writer != null);
renderInput(context, writer, radio, clientId, itemValue, converter, checked, disabled, group);
if (currentItem.getLabel() != null) {
renderLabel(writer, radio, clientId, currentItem, new OptionComponentInfo(radio));
}
}
protected boolean isChecked(FacesContext context, UISelectOne radio, Object itemValue) {
Object currentValue = radio.getSubmittedValue();
if (currentValue == null) {
currentValue = radio.getValue();
}
Class type = String.class;
if (currentValue != null) {
type = currentValue.getClass();
if (type.isArray()) {
currentValue = ((Object[]) currentValue)[0];
if (null != currentValue) {
type = currentValue.getClass();
}
}
else if (Collection.class.isAssignableFrom(type)) {
Iterator valueIter = ((Collection) currentValue).iterator();
if ((null != valueIter) && valueIter.hasNext()) {
currentValue = valueIter.next();
if (null != currentValue) {
type = currentValue.getClass();
}
}
}
}
RequestStateManager.set(context, RequestStateManager.TARGET_COMPONENT_ATTRIBUTE_NAME, radio);
Object newValue;
try {
newValue = context.getApplication().getExpressionFactory().coerceToType(itemValue, type);
}
catch (ELException | IllegalArgumentException e) {
// If coerceToType fails, per the docs it should throw an ELException, however, SJAS 9.0 and 9.0u1 will
// throw an IllegalArgumentException instead (see https://java.net/jira/browse/GLASSFISH-1527).
newValue = itemValue;
}
return (newValue != null) && newValue.equals(currentValue);
}
@Override
protected void renderOption(FacesContext context,
UIComponent component,
Converter converter,
SelectItem curItem,
Object currentSelections,
Object[] submittedValues,
boolean alignVertical,
int itemNumber,
OptionComponentInfo optionInfo) throws IOException {
ResponseWriter writer = context.getResponseWriter();
assert (writer != null);
UISelectOne selectOne = (UISelectOne) component;
Object curValue = curItem.getValue();
boolean checked = isChecked(context, selectOne, curValue);
if (optionInfo.isHideNoSelection()
&& curItem.isNoSelectionOption()
&& curValue != null
&& !checked) {
return;
}
if (alignVertical) {
writer.writeText("\t", component, null);
writer.startElement("tr", component);
writer.writeText("\n", component, null);
}
writer.startElement("td", component);
writer.writeText("\n", component, null);
String clientId = component.getClientId(context) + UINamingContainer.getSeparatorChar(context) + Integer.toString(itemNumber);
// Don't render the disabled attribute twice if the 'parent' component is already marked disabled.
boolean disabled = !optionInfo.isDisabled() && curItem.isDisabled();
renderInput(context, writer, component, clientId, curValue, converter, checked, disabled, null);
renderLabel(writer, component, clientId, curItem, optionInfo);
writer.endElement("td");
writer.writeText("\n", component, null);
if (alignVertical) {
writer.writeText("\t", component, null);
writer.endElement("tr");
writer.writeText("\n", component, null);
}
}
protected void renderInput(FacesContext context, ResponseWriter writer, UIComponent component, String clientId, Object itemValue,
Converter converter, boolean checked, boolean disabled, Group group) throws IOException
{
writer.startElement("input", component);
writer.writeAttribute("type", "radio", "type");
if (checked) {
writer.writeAttribute("checked", Boolean.TRUE, null);
}
Object value = (getFormattedValue(context, component, itemValue, converter));
if (group == null) {
writer.writeAttribute("name", component.getClientId(context), "clientId");
writer.writeAttribute("id", clientId, "id");
writer.writeAttribute("value", value, "value");
}
else {
writer.writeAttribute("name", group.getClientName(), "group");
writer.writeAttribute("id", clientId, "id");
writer.writeAttribute("value", clientId + UINamingContainer.getSeparatorChar(context) + value, "value");
}
if (disabled) {
writer.writeAttribute("disabled", true, "disabled");
}
// Apply HTML 4.x attributes specified on UISelectMany component to all
// items in the list except styleClass and style which are rendered as
// attributes of outer most table.
RenderKitUtils.renderPassThruAttributes(context,
writer,
component,
ATTRIBUTES,
getNonOnClickSelectBehaviors(component));
RenderKitUtils.renderXHTMLStyleBooleanAttributes(writer,
component);
RenderKitUtils.renderSelectOnclick(context, component, false);
writer.endElement("input");
}
protected void renderLabel(ResponseWriter writer, UIComponent component, String forClientId, SelectItem curItem,
OptionComponentInfo optionInfo) throws IOException
{
String labelClass;
if (optionInfo.isDisabled() || curItem.isDisabled()) {
labelClass = optionInfo.getDisabledClass();
} else {
labelClass = optionInfo.getEnabledClass();
}
writer.startElement("label", component);
writer.writeAttribute("for", forClientId, "for");
// if enabledClass or disabledClass attributes are specified, apply
// it on the label.
if (labelClass != null) {
writer.writeAttribute("class", labelClass, "labelClass");
}
String itemLabel = curItem.getLabel();
if (itemLabel != null) {
writer.writeText(" ", component, null);
if (!curItem.isEscape()) {
// It seems the ResponseWriter API should
// have a writeText() with a boolean property
// to determine if it content written should
// be escaped or not.
writer.write(itemLabel);
} else {
writer.writeText(itemLabel, component, "label");
}
}
writer.endElement("label");
}
protected static Group getGroup(FacesContext context, UISelectOne radio) {
String groupName = radio.getGroup();
if (groupName == null) {
return null;
}
UIComponent groupContainer = RenderKitUtils.getForm(radio, context);
if (groupContainer == null) {
groupContainer = context.getViewRoot();
}
String clientName = groupContainer.getClientId(context) + UINamingContainer.getSeparatorChar(context) + groupName;
Map radioButtonGroups = RequestStateManager.get(context, RequestStateManager.PROCESSED_RADIO_BUTTON_GROUPS);
if (radioButtonGroups == null) {
radioButtonGroups = new HashMap<>();
RequestStateManager.set(context, RequestStateManager.PROCESSED_RADIO_BUTTON_GROUPS, radioButtonGroups);
}
Group group = radioButtonGroups.get(clientName);
if (group == null) {
group = new Group(context, clientName);
radioButtonGroups.put(clientName, group);
}
return group;
}
/**
* Keeps track of all detail.
*/
protected static class Group {
private final String clientName;
private final List clientIds;
private ValueExpression value;
public Group(FacesContext context, String clientName) {
this.clientName = clientName;
this.clientIds = new ArrayList<>();
}
public String getClientName() {
return clientName;
}
public void addRadio(FacesContext context, UISelectOne radio) {
String clientId = radio.getClientId(context);
if (!clientIds.contains(clientId)) {
if (clientIds.isEmpty()) {
value = radio.getValueExpression("value");
}
else if (radio.getValueExpression("value") == null) {
radio.setValueExpression("value", value);
}
if (!RenderKitUtils.getSelectItems(context, radio).hasNext()) {
radio.getChildren().add(new GroupSelectItem());
}
clientIds.add(clientId);
radio.getAttributes().put(GroupSelectItem.class.getName(), Collections.unmodifiableList(clientIds));
}
}
}
/**
* Used when a doesn't have a select item; it will then get it via first radio of the group.
*/
public static class GroupSelectItem extends UISelectItem {
private SelectItem selectItem;
@SuppressWarnings("unchecked")
private SelectItem getSelectItem() {
if (selectItem == null) {
FacesContext context = getFacesContext();
UISelectOne radio = (UISelectOne) getParent();
List groupClientIds = (List) radio.getAttributes().get(GroupSelectItem.class.getName());
UIComponent firstRadioOfGroup = context.getViewRoot().findComponent(groupClientIds.get(0));
SelectItemsIterator iterator = RenderKitUtils.getSelectItems(context, firstRadioOfGroup);
int index = groupClientIds.indexOf(radio.getClientId(context));
while (index-- > 0 && iterator.hasNext()) {
iterator.next();
}
if (!iterator.hasNext()) {
throw new IllegalStateException(MessageFormat.format(
"UISelectOne component id=\"{0}\" group=\"{1}\" has no UISelectItem",
new Object[] { radio.getId(), radio.getGroup() }));
}
selectItem = iterator.next();
}
return selectItem;
}
@Override
public Object getItemValue() {
return getSelectItem().getValue();
}
@Override
public String getItemLabel() {
return getSelectItem().getLabel();
}
@Override
public String getItemDescription() {
return getSelectItem().getDescription();
}
@Override
public boolean isItemEscaped() {
return getSelectItem().isEscape();
}
@Override
public boolean isNoSelectionOption() {
return getSelectItem().isNoSelectionOption();
}
@Override
public boolean isItemDisabled() {
return getSelectItem().isDisabled();
}
}
} // end of class RadioRenderer