001/*
002 * (C) Copyright 2017 Nuxeo (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 *     Thomas Roger
016 */
017
018package org.nuxeo.ecm.platform.ui.web.renderer;
019
020import java.io.IOException;
021import java.util.Map;
022
023import javax.faces.component.UIComponent;
024import javax.faces.component.UIInput;
025import javax.faces.component.UIOutput;
026import javax.faces.component.html.HtmlInputFile;
027import javax.faces.context.FacesContext;
028import javax.faces.context.ResponseWriter;
029
030import com.sun.faces.renderkit.Attribute;
031import com.sun.faces.renderkit.AttributeManager;
032import com.sun.faces.renderkit.RenderKitUtils;
033import com.sun.faces.renderkit.html_basic.TextRenderer;
034
035/**
036 * Override default {@code TextRenderer} to handle HTML5 attributes.
037 *
038 * @since 5.7
039 */
040public class NXTextRenderer extends TextRenderer {
041
042    private static final Attribute[] INPUT_ATTRIBUTES = AttributeManager.getAttributes(AttributeManager.Key.INPUTTEXT);
043
044    private static final Attribute[] OUTPUT_ATTRIBUTES = AttributeManager.getAttributes(
045            AttributeManager.Key.OUTPUTTEXT);
046
047    @Override
048    protected void getEndTextToRender(FacesContext context, UIComponent component, String currentValue)
049            throws IOException {
050        ResponseWriter writer = context.getResponseWriter();
051        assert (writer != null);
052        boolean shouldWriteIdAttribute = false;
053        boolean isOutput = false;
054
055        String style = (String) component.getAttributes().get("style");
056        String styleClass = (String) component.getAttributes().get("styleClass");
057        String dir = (String) component.getAttributes().get("dir");
058        String lang = (String) component.getAttributes().get("lang");
059        String title = (String) component.getAttributes().get("title");
060        String placeholder = (String) component.getAttributes().get("placeholder");
061        Map<String, Object> passthroughAttributes = component.getPassThroughAttributes(false);
062        boolean hasPassthroughAttributes = null != passthroughAttributes && !passthroughAttributes.isEmpty();
063        if (component instanceof UIInput) {
064            writer.startElement("input", component);
065            writeIdAttributeIfNecessary(context, writer, component);
066
067            if (component instanceof HtmlInputFile) {
068                writer.writeAttribute("type", "file", null);
069            } else {
070                writer.writeAttribute("type", "text", null);
071            }
072            writer.writeAttribute("name", (component.getClientId(context)), "clientId");
073
074            // only output the autocomplete attribute if the value
075            // is 'off' since its lack of presence will be interpreted
076            // as 'on' by the browser
077            if ("off".equals(component.getAttributes().get("autocomplete"))) {
078                writer.writeAttribute("autocomplete", "off", "autocomplete");
079            }
080
081            // render default text specified
082            if (currentValue != null) {
083                writer.writeAttribute("value", currentValue, "value");
084            }
085            if (null != styleClass) {
086                writer.writeAttribute("class", styleClass, "styleClass");
087            }
088
089            // HTML5 attributes not handled by default TextRenderer
090            if (null != placeholder) {
091                writer.writeAttribute("placeholder", placeholder, "placeholder");
092            }
093
094            // style is rendered as a passthur attribute
095            RenderKitUtils.renderPassThruAttributes(context, writer, component, INPUT_ATTRIBUTES,
096                    getNonOnChangeBehaviors(component));
097            RenderKitUtils.renderXHTMLStyleBooleanAttributes(writer, component);
098
099            RenderKitUtils.renderOnchange(context, component, false);
100
101            writer.endElement("input");
102
103        } else if (isOutput = (component instanceof UIOutput)) {
104            if (styleClass != null || style != null || dir != null || lang != null || title != null
105                    || hasPassthroughAttributes || (shouldWriteIdAttribute = shouldWriteIdAttribute(component))) {
106                writer.startElement("span", component);
107                writeIdAttributeIfNecessary(context, writer, component);
108                if (null != styleClass) {
109                    writer.writeAttribute("class", styleClass, "styleClass");
110                }
111                // style is rendered as a passthru attribute
112                RenderKitUtils.renderPassThruAttributes(context, writer, component, OUTPUT_ATTRIBUTES);
113
114            }
115            if (currentValue != null) {
116                Object val = component.getAttributes().get("escape");
117                if ((val != null) && Boolean.valueOf(val.toString())) {
118                    writer.writeText(currentValue, component, "value");
119                } else {
120                    writer.write(currentValue);
121                }
122            }
123        }
124        if (isOutput && (styleClass != null || style != null || dir != null || lang != null || title != null
125                || hasPassthroughAttributes || (shouldWriteIdAttribute))) {
126            writer.endElement("span");
127        }
128    }
129
130}