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

de.larmic.butterfaces.component.renderkit.html_basic.action.CommandLinkRenderer Maven / Gradle / Ivy

The newest version!
/*
 * Copyright Lars Michaelis and Stephan Zerhusen 2016.
 * Distributed under the MIT License.
 * (See accompanying file README.md file or copy at http://opensource.org/licenses/MIT)
 */
package de.larmic.butterfaces.component.renderkit.html_basic.action;

import de.larmic.butterfaces.component.base.renderer.HtmlBasicRenderer;
import de.larmic.butterfaces.component.behavior.JsfAjaxRequest;
import de.larmic.butterfaces.component.html.action.HtmlCommandLink;
import de.larmic.butterfaces.resolver.AjaxClientIdResolver;
import de.larmic.butterfaces.resolver.ClientBehaviorResolver;
import de.larmic.butterfaces.resolver.UIComponentResolver;
import de.larmic.butterfaces.resolver.WebXmlParameters;
import de.larmic.butterfaces.util.StringJoiner;
import de.larmic.butterfaces.util.StringUtils;

import javax.faces.component.*;
import javax.faces.component.behavior.AjaxBehavior;
import javax.faces.component.behavior.ClientBehaviorContext;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.event.ActionEvent;
import javax.faces.render.FacesRenderer;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;

/**
 * @author Lars Michaelis
 */
@FacesRenderer(componentFamily = HtmlCommandLink.COMPONENT_FAMILY, rendererType = HtmlCommandLink.RENDERER_TYPE)
public class CommandLinkRenderer extends HtmlBasicRenderer {

    private WebXmlParameters webXmlParameters;

    /**
     * Will be set in renderAsActive if f:ajax child with onevent attribute exists.
     */
    private String onEventCallback = null;

    @Override
    public void encodeBegin(FacesContext context, UIComponent component) throws IOException {
        this.onEventCallback = null;

        final HtmlCommandLink link = (HtmlCommandLink) component;
        final ResponseWriter writer = context.getResponseWriter();

        webXmlParameters = new WebXmlParameters(context.getExternalContext());

        if (!link.isDisabled()) {
            this.renderAsActive(context, component);
        } else {
            writer.startElement("span", component);
            writeIdAttributeIfNecessary(context, writer, component);
            writeStyleClass(writer, component);
            if (StringUtils.isNotEmpty(link.getStyle())) {
                writer.writeAttribute("style", link.getStyle(), "style");
            }
            this.writeValue(component, writer);
        }
    }

    @Override
    protected boolean shouldWriteIdAttribute(UIComponent component) {
        return true;
    }

    @Override
    public void decode(FacesContext context, UIComponent component) {
        final HtmlCommandLink link = (HtmlCommandLink) component;

        if (link.isDisabled()) {
            return;
        }

        final ExternalContext external = context.getExternalContext();
        final Map params = external.getRequestParameterMap();
        final String resetValues = params.get("javax.faces.partial.resetValues");
        final String render = params.get("javax.faces.partial.render");

        if (StringUtils.isNotEmpty(resetValues) && StringUtils.isNotEmpty(render) && Boolean.valueOf(resetValues)) {
            final String[] split = render.split(" ");

            for (String clientId : split) {
                final UIComponent renderComponent = context.getViewRoot().findComponent(clientId);
                resetValues(renderComponent);
            }
        }

        String clientId = decodeBehaviors(context, component);

        if (clientId == null) {
            clientId = component.getClientId(context);
        }

        if (params.containsKey(clientId) || isPartialOrBehaviorAction(clientId, params)) {
            component.queueEvent(new ActionEvent(component));
        }
    }

    private boolean isPartialOrBehaviorAction(String clientId, Map requestParameter) {
        if (clientId == null || clientId.length() == 0) {
            return false;
        }

        if (!clientId.equals(requestParameter.get("javax.faces.source"))) {
            return false;
        }

        // check for a behavior action event
        final String behaviorEvent = requestParameter.get("javax.faces.behavior.event");
        if (behaviorEvent != null) {
            return "action".equals(behaviorEvent);
        }

        // Not a Behavior-related request. Check for jsf.ajax.request() request params
        return ("click".equals(requestParameter.get("javax.faces.partial.event")));
    }

    private void resetValues(final UIComponent component) {
        if (component == null) {
            return;
        }

        for (UIComponent child : component.getChildren()) {
            resetValues(child);
        }

        if (component instanceof UIOutput) {
            ((UIOutput) component).resetValue();
        } else if (component instanceof EditableValueHolder) {
            ((EditableValueHolder) component).resetValue();
        }
    }

    @Override
    public void encodeEnd(final FacesContext context, final UIComponent component) throws IOException {
        final HtmlCommandLink link = (HtmlCommandLink) component;
        final ResponseWriter writer = context.getResponseWriter();

        if (!link.isDisabled()) {
            if (link.isAjaxDisableLinkOnRequest()) {
                writer.startElement("script", component);
                writer.writeText("function " + getOnEventListenerName(component) + "(data) {", null);
                if (StringUtils.isNotEmpty(onEventCallback)) {
                    writer.writeText("    " + onEventCallback + "(data);", null);
                }

                final String processingText = createAjaxProcessingText(link);
                final String processingGlyphicon = createAjaxProcessingGlypicon(link);

                final AjaxClientIdResolver ajaxClientIdResolver = new AjaxClientIdResolver(link);
                final String jQueryIDSelector = isAjaxDisableRenderReqionOnRequest(link, webXmlParameters)
                        ? ajaxClientIdResolver.getjQueryRenderIDSelector() : "undefined";

                writer.writeText(
                        createDisableOnClickFunctionCall(link, processingText, processingGlyphicon, jQueryIDSelector),
                        null
                );

                writer.writeText("}", null);
                writer.endElement("script");
            }
            writer.endElement("a");
        } else {
            writer.endElement("span");
        }
    }

    private Boolean isAjaxDisableRenderReqionOnRequest(final HtmlCommandLink link, final WebXmlParameters parameters) {
        final Boolean disableRegion = link.isAjaxDisableRenderRegionsOnRequest();
        return disableRegion != null ? disableRegion : parameters.isAjaxDisableRenderRegionsOnRequest();
    }

    private String createDisableOnClickFunctionCall(HtmlCommandLink link, String processingText,
                                                    String processingGlyphicon, String jQueryIDSelector) {
        final StringBuilder sb = new StringBuilder();
        sb.append("    butter.link.disableOnClick(data, ");
        sb.append(link.isAjaxShowWaitingDotsOnRequest()).append(",");

        if (link.getValue() != null) {
            sb.append("'").append(link.getValue()).append("'");
        } else {
            sb.append("null");
        }
        sb.append(",'");

        sb.append(processingText).append("','");
        sb.append(link.getGlyphicon()).append("','");
        sb.append(processingGlyphicon).append("',");
        sb.append(link.isAjaxHideGlyphiconOnRequest()).append(",'");
        sb.append(jQueryIDSelector).append("');");

        return sb.toString();
    }

    private String createAjaxProcessingText(final HtmlCommandLink link) {
        if (StringUtils.isNotEmpty(link.getAjaxProcessingTextOnRequest())) {
            return link.getAjaxProcessingTextOnRequest();
        }

        return webXmlParameters.getAjaxProcessingTextOnRequest();
    }

    private String createAjaxProcessingGlypicon(final HtmlCommandLink link) {
        if (StringUtils.isNotEmpty(link.getAjaxProcessingGlyphiconOnRequest())) {
            return link.getAjaxProcessingGlyphiconOnRequest();
        }

        return webXmlParameters.getAjaxProcessingGlyphiconOnRequest();
    }

    private void writeValue(final UIComponent component, final ResponseWriter writer) throws IOException {
        final HtmlCommandLink commandLink = (HtmlCommandLink) component;

        this.writeGlyphiconIfNecessary(commandLink, writer);

        writer.startElement("span", component);
        writer.writeAttribute("class", "butter-component-glyphicon-text", null);

        final Object value = ((UICommand) component).getValue();
        if (value != null) {
            final String label = value.toString();

            if (StringUtils.isNotEmpty(label)) {
                writer.writeText(label, component, null);
            }
        }

        writer.endElement("span");

        this.writeWaitingDotsIfNecessary(commandLink, writer);
    }

    private void renderAsActive(final FacesContext context,
                                final UIComponent component) throws IOException {
        final HtmlCommandLink link = (HtmlCommandLink) component;
        final ResponseWriter writer = context.getResponseWriter();
        final AjaxBehavior ajaxBehavior = ClientBehaviorResolver.findFirstActiveAjaxBehavior(link, "action");

        if (link.isAjaxDisableLinkOnRequest() && ajaxBehavior != null) {
            if (StringUtils.isNotEmpty(ajaxBehavior.getOnevent())) {
                onEventCallback = ajaxBehavior.getOnevent();
            }

            ajaxBehavior.setOnevent(getOnEventListenerName(component));
            ajaxBehavior.setOnerror(getOnEventListenerName(component));
        }

        // TODO check resetValues

        writer.startElement("a", link);
        writeIdAttributeIfNecessary(context, writer, link);
        writer.writeAttribute("href", "#", "href");

        this.renderBooleanValue(component, writer, "disabled");
        this.renderBooleanValue(component, writer, "ismap");

        this.renderStringValue(component, writer, "title");
        this.renderStringValue(component, writer, "tabindex");
        this.renderStringValue(component, writer, "style");
        this.renderStringValue(component, writer, "target");

        // html link attributes
        this.renderStringValue(component, writer, "accesskey");
        this.renderStringValue(component, writer, "charset");
        this.renderStringValue(component, writer, "coords");
        this.renderStringValue(component, writer, "dir");
        this.renderStringValue(component, writer, "hreflang");
        this.renderStringValue(component, writer, "lang");
        this.renderStringValue(component, writer, "rel");
        this.renderStringValue(component, writer, "rev");
        this.renderStringValue(component, writer, "shape");
        this.renderStringValue(component, writer, "type");

        this.renderEventValue(component, writer, "onkeydown", "keydown");
        this.renderEventValue(component, writer, "onkeyup", "keyup");
        this.renderEventValue(component, writer, "onblur", "blur");
        if (ajaxBehavior != null) {
            this.renderOnClickEventValue(component, writer, new JsfAjaxRequest(link, ajaxBehavior, "action").toString());
        } else {
            final String submitHandler = buildJavaScriptFormSubmitCall(context, component, getTrimmedTarget(component));
            this.renderOnClickEventValue(component, writer, submitHandler);
        }
        this.renderEventValue(component, writer, "ondblclick", "dblclick");
        this.renderEventValue(component, writer, "onfocus", "focus");
        this.renderEventValue(component, writer, "onkeypress", "keypress");
        this.renderEventValue(component, writer, "onmousedown", "mousedown");
        this.renderEventValue(component, writer, "onmousemove", "mousemove");
        this.renderEventValue(component, writer, "onmouseout", "mouseout");
        this.renderEventValue(component, writer, "onmouseover", "mouseover");
        this.renderEventValue(component, writer, "onmouseup", "mouseup");

        writeStyleClass(writer, link);

        // render the current value as link text.
        writeValue(link, writer);

        // reset ajax behaviour because otherwise a render of this component will not be work correctly (wrong js
        // callback is registered if onevent is set on f:ajax.
        if (ajaxBehavior != null) {
            ajaxBehavior.setOnevent(onEventCallback);
        }
    }

    private String getTrimmedTarget(final UIComponent component) throws IOException {
        final Object target = component.getAttributes().get("target");
        if (target != null && StringUtils.isNotEmpty(target.toString())) {
            return target.toString().trim();
        }

        return null;
    }

    private static String buildJavaScriptFormSubmitCall(FacesContext context,
                                                        UIComponent component,
                                                        String submitTarget) {
        final Collection params = findBehaviorParameters(component);
        final StringBuilder builder = new StringBuilder(256);

        final String formClientId = UIComponentResolver.getFormClientId(component, context);
        final String componentClientId = component.getClientId(context);

        builder.append("ButterFaces.CommandLink.submitForm('");
        builder.append(formClientId);
        builder.append("',{");

        ParameterAppender.appendProperty(builder, componentClientId, componentClientId);

        if ((null != params) && (!params.isEmpty())) {
            for (ClientBehaviorContext.Parameter param : params) {
                ParameterAppender.appendProperty(builder, param.getName(), param.getValue());
            }
        }

        builder.append("},'");

        if (submitTarget != null) {
            builder.append(submitTarget);
        }

        builder.append("')");

        return builder.toString();
    }

    private static Collection findBehaviorParameters(final UIComponent component) {
        final int childCount = component.getChildCount();
        ArrayList params = null;

        if (childCount > 0) {
            for (UIComponent kid : component.getChildren()) {
                if (kid instanceof UIParameter) {
                    final UIParameter uiParam = (UIParameter) kid;
                    final String name = uiParam.getName();
                    final Object value = uiParam.getValue();

                    if (StringUtils.isNotEmpty(name)) {
                        if (params == null) {
                            params = new ArrayList<>(childCount);
                        }

                        params.add(new ClientBehaviorContext.Parameter(name, value));
                    }
                }
            }
        }

        return params == null ? Collections.emptyList() : params;

    }

    private void renderOnClickEventValue(UIComponent component, ResponseWriter writer, String onClickEvent) throws IOException {
        final String componentEventFunction = createComponentEventFunction(component, "onclick");
        if (componentEventFunction != null) {
            writer.writeAttribute("onclick", onClickEvent + ";" + componentEventFunction + ";return false", "onclick");
        } else {
            writer.writeAttribute("onclick", onClickEvent + ";return false", "onclick");
        }
    }

    private void writeStyleClass(final ResponseWriter writer, final UIComponent component) throws IOException {
        final HtmlCommandLink link = (HtmlCommandLink) component;
        final String styleClass = (String) component.getAttributes().get("styleClass");

        StringJoiner generatedStyleClassJoiner = StringJoiner.on(' ').join(StringUtils.getNotNullValue(styleClass, ""));

        if (link.isDisabled()) {
            generatedStyleClassJoiner = generatedStyleClassJoiner.join("btn-disabled");
        }
        if (StringUtils.isEmpty(link.getGlyphicon())) {
            generatedStyleClassJoiner = generatedStyleClassJoiner.join("no-glyphicon");
        }

        final String generatedStyleClass = generatedStyleClassJoiner.toString();
        if (generatedStyleClass.length() > 0) {
            writer.writeAttribute("class", generatedStyleClass, "styleClass");
        }
    }

    private String getOnEventListenerName(final UIComponent component) {
        final char separatorChar = UINamingContainer.getSeparatorChar(FacesContext.getCurrentInstance());
        return "glyphiconLinkListener" + "_" + component.getClientId().replace(separatorChar + "", "_");
    }

    private void writeWaitingDotsIfNecessary(final HtmlCommandLink commandLink,
                                             final ResponseWriter writer) throws IOException {
        if (commandLink.isAjaxDisableLinkOnRequest()) {
            writer.startElement("span", commandLink);
            writer.writeAttribute("class", "butter-component-glyphicon-processing", null);
            writer.endElement("span");
        }
    }

    private void writeGlyphiconIfNecessary(final HtmlCommandLink commandLink,
                                           final ResponseWriter writer) throws IOException {
        final String glyphicon = StringUtils.getNotNullValue(commandLink.getGlyphicon(), "");

        writer.startElement("span", commandLink);
        writer.writeAttribute("class", "butter-component-glyphicon " + glyphicon, null);
        writer.endElement("span");
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy