001/*
002 * (C) Copyright 2013 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl-2.1.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Anahide Tchertchian
016 */
017package org.nuxeo.ecm.platform.ui.web.renderer;
018
019import java.io.IOException;
020import java.util.Collection;
021import java.util.Iterator;
022
023import javax.el.ELException;
024import javax.faces.component.NamingContainer;
025import javax.faces.component.UIComponent;
026import javax.faces.component.UINamingContainer;
027import javax.faces.component.UISelectOne;
028import javax.faces.context.FacesContext;
029import javax.faces.context.ResponseWriter;
030import javax.faces.convert.Converter;
031import javax.faces.model.SelectItem;
032
033import com.sun.faces.renderkit.Attribute;
034import com.sun.faces.renderkit.AttributeManager;
035import com.sun.faces.renderkit.RenderKitUtils;
036import com.sun.faces.renderkit.html_basic.RadioRenderer;
037import com.sun.faces.util.RequestStateManager;
038
039/**
040 * Renderer that does not ignore the converter set on the component on submit
041 *
042 * @since 5.8
043 */
044public class NXRadioRenderer extends RadioRenderer {
045
046    private static final Attribute[] ATTRIBUTES = AttributeManager.getAttributes(AttributeManager.Key.SELECTONERADIO);
047
048    @Override
049    protected void renderOption(FacesContext context, UIComponent component, Converter converter, SelectItem curItem,
050            Object currentSelections, Object[] submittedValues, boolean alignVertical, int itemNumber,
051            OptionComponentInfo optionInfo) throws IOException {
052
053        ResponseWriter writer = context.getResponseWriter();
054        assert (writer != null);
055
056        UISelectOne selectOne = (UISelectOne) component;
057        Object curValue = selectOne.getSubmittedValue();
058        if (curValue == null) {
059            curValue = selectOne.getValue();
060            // XXX added for NXRadioRenderer
061            if (converter != null) {
062                curValue = converter.getAsString(context, component, curValue);
063            }
064        }
065
066        Class type = String.class;
067        if (curValue != null) {
068            type = curValue.getClass();
069            if (type.isArray()) {
070                curValue = ((Object[]) curValue)[0];
071                if (null != curValue) {
072                    type = curValue.getClass();
073                }
074            } else if (Collection.class.isAssignableFrom(type)) {
075                Iterator valueIter = ((Collection) curValue).iterator();
076                if (null != valueIter && valueIter.hasNext()) {
077                    curValue = valueIter.next();
078                    if (null != curValue) {
079                        type = curValue.getClass();
080                    }
081                }
082            }
083        }
084        Object itemValue = curItem.getValue();
085        RequestStateManager.set(context, RequestStateManager.TARGET_COMPONENT_ATTRIBUTE_NAME, component);
086        Object newValue;
087        try {
088            newValue = context.getApplication().getExpressionFactory().coerceToType(itemValue, type);
089        } catch (ELException ele) {
090            newValue = itemValue;
091        } catch (IllegalArgumentException iae) {
092            // If coerceToType fails, per the docs it should throw
093            // an ELException, however, GF 9.0 and 9.0u1 will throw
094            // an IllegalArgumentException instead (see GF issue 1527).
095            newValue = itemValue;
096        }
097
098        boolean checked = null != newValue && newValue.equals(curValue);
099
100        // FIXME: curItem.isNoSelectionOption() method not found
101        // if (optionInfo.isHideNoSelection() && curItem.isNoSelectionOption()
102        // && curValue != null && !checked) {
103        // return;
104        // }
105        if (optionInfo.isHideNoSelection() && curValue != null && !checked) {
106            return;
107        }
108
109        if (alignVertical) {
110            writer.writeText("\t", component, null);
111            writer.startElement("tr", component);
112            writer.writeText("\n", component, null);
113        }
114
115        String labelClass;
116        if (optionInfo.isDisabled() || curItem.isDisabled()) {
117            labelClass = optionInfo.getDisabledClass();
118        } else {
119            labelClass = optionInfo.getEnabledClass();
120        }
121        writer.startElement("td", component);
122        writer.writeText("\n", component, null);
123
124        writer.startElement("input", component);
125        writer.writeAttribute("type", "radio", "type");
126
127        if (checked) {
128            writer.writeAttribute("checked", Boolean.TRUE, null);
129        }
130        writer.writeAttribute("name", component.getClientId(context), "clientId");
131        String idString = component.getClientId(context) + NamingContainer.SEPARATOR_CHAR
132                + Integer.toString(itemNumber);
133        writer.writeAttribute("id", idString, "id");
134
135        writer.writeAttribute("value", (getFormattedValue(context, component, curItem.getValue(), converter)), "value");
136
137        // Don't render the disabled attribute twice if the 'parent'
138        // component is already marked disabled.
139        if (!optionInfo.isDisabled()) {
140            if (curItem.isDisabled()) {
141                writer.writeAttribute("disabled", true, "disabled");
142            }
143        }
144        // Apply HTML 4.x attributes specified on UISelectMany component to all
145        // items in the list except styleClass and style which are rendered as
146        // attributes of outer most table.
147        RenderKitUtils.renderPassThruAttributes(context, writer, component, ATTRIBUTES,
148                getNonOnClickSelectBehaviors(component));
149        RenderKitUtils.renderXHTMLStyleBooleanAttributes(writer, component);
150
151        RenderKitUtils.renderSelectOnclick(context, component, false);
152
153        writer.endElement("input");
154        writer.startElement("label", component);
155        writer.writeAttribute("for", idString, "for");
156        // if enabledClass or disabledClass attributes are specified, apply
157        // it on the label.
158        if (labelClass != null) {
159            writer.writeAttribute("class", labelClass, "labelClass");
160        }
161        String itemLabel = curItem.getLabel();
162        if (itemLabel != null) {
163            writer.writeText(" ", component, null);
164            if (!curItem.isEscape()) {
165                // It seems the ResponseWriter API should
166                // have a writeText() with a boolean property
167                // to determine if it content written should
168                // be escaped or not.
169                writer.write(itemLabel);
170            } else {
171                writer.writeText(itemLabel, component, "label");
172            }
173        }
174        writer.endElement("label");
175        writer.endElement("td");
176        writer.writeText("\n", component, null);
177        if (alignVertical) {
178            writer.writeText("\t", component, null);
179            writer.endElement("tr");
180            writer.writeText("\n", component, null);
181        }
182    }
183
184}