All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.faces.renderkit.html_basic.RadioRenderer Maven / Gradle / Ivy

Go to download

This is the master POM file for Oracle's Implementation of the JSF 2.2 Specification.

There is a newer version: 2.4.0
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

// 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