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