| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.myfaces.renderkit.html.base; |
| |
| import org.apache.myfaces.renderkit.html.util.CommonPropertyUtils; |
| import java.io.IOException; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.Map; |
| |
| import javax.faces.component.UICommand; |
| import javax.faces.component.UIComponent; |
| import javax.faces.component.UIForm; |
| import javax.faces.component.UIParameter; |
| import javax.faces.component.ValueHolder; |
| import javax.faces.component.behavior.ClientBehavior; |
| import javax.faces.component.behavior.ClientBehaviorContext; |
| import javax.faces.component.behavior.ClientBehaviorHolder; |
| import javax.faces.component.html.HtmlCommandButton; |
| import javax.faces.component.html.HtmlCommandLink; |
| import javax.faces.context.FacesContext; |
| import javax.faces.context.ResponseWriter; |
| import javax.faces.event.ActionEvent; |
| |
| import org.apache.myfaces.renderkit.ClientBehaviorEvents; |
| import org.apache.myfaces.renderkit.html.util.JSFAttr; |
| import org.apache.myfaces.renderkit.RendererUtils; |
| import org.apache.myfaces.renderkit.html.util.JavascriptUtils; |
| import org.apache.myfaces.renderkit.html.util.ResourceUtils; |
| import org.apache.myfaces.renderkit.html.util.HTML; |
| import org.apache.myfaces.util.ComponentUtils; |
| import org.apache.myfaces.util.SharedStringBuilder; |
| |
| public class HtmlButtonRendererBase extends HtmlRenderer |
| { |
| private static final String SB_BUILD_BEHAVIORIZED_ONCLICK = HtmlButtonRendererBase.class.getName() |
| + "#buildBehaviorizedOnClick"; |
| private static final String SB_BUILD_ONCLICK = HtmlButtonRendererBase.class.getName() |
| + "#buildOnClick"; |
| private static final String SB_ADD_CHILD_PARAMETERS = HtmlButtonRendererBase.class.getName() + |
| "#addChildParameters"; |
| |
| private static final String IMAGE_BUTTON_SUFFIX_X = ".x"; |
| private static final String IMAGE_BUTTON_SUFFIX_Y = ".y"; |
| |
| @Override |
| public void decode(FacesContext facesContext, UIComponent uiComponent) |
| { |
| RendererUtils.checkParamValidity(facesContext, uiComponent, UICommand.class); |
| |
| //super.decode must not be called, because value is handled here |
| boolean disabled = isDisabled(facesContext, uiComponent); |
| // MYFACES-3960 Decode, decode client behavior and queue action event at the end |
| boolean activateActionEvent = !isReset(uiComponent) && isSubmitted(facesContext, uiComponent) && !disabled; |
| |
| if (uiComponent instanceof ClientBehaviorHolder && |
| !disabled) |
| { |
| HtmlRendererUtils.decodeClientBehaviors(facesContext, uiComponent); |
| } |
| |
| if (activateActionEvent) |
| { |
| uiComponent.queueEvent(new ActionEvent(uiComponent)); |
| } |
| } |
| |
| private static boolean isReset(UIComponent uiComponent) |
| { |
| return "reset".equals((String) uiComponent.getAttributes().get(HTML.TYPE_ATTR)); |
| } |
| |
| private static boolean isButton(UIComponent uiComponent) |
| { |
| return "button".equals((String) uiComponent.getAttributes().get(HTML.TYPE_ATTR)); |
| } |
| |
| private static boolean isSubmitted(FacesContext facesContext, UIComponent uiComponent) |
| { |
| String clientId = uiComponent.getClientId(facesContext); |
| Map paramMap = facesContext.getExternalContext().getRequestParameterMap(); |
| String hiddenLink = null; |
| |
| UIForm form = ComponentUtils.closest(UIForm.class, uiComponent); |
| if (form != null) |
| { |
| hiddenLink = (String) facesContext.getExternalContext().getRequestParameterMap().get( |
| HtmlRendererUtils.getHiddenCommandLinkFieldName(form, facesContext)); |
| } |
| return paramMap.containsKey(clientId) || paramMap.containsKey(clientId + IMAGE_BUTTON_SUFFIX_X) |
| || paramMap.containsKey(clientId + IMAGE_BUTTON_SUFFIX_Y) |
| || (hiddenLink != null && hiddenLink.equals (clientId)) |
| || HtmlRendererUtils.isPartialOrBehaviorSubmit(facesContext, clientId); |
| } |
| |
| @Override |
| public void encodeBegin(FacesContext facesContext, UIComponent uiComponent) throws IOException |
| { |
| RendererUtils.checkParamValidity(facesContext, uiComponent, UICommand.class); |
| |
| String clientId = uiComponent.getClientId(facesContext); |
| |
| ResponseWriter writer = facesContext.getResponseWriter(); |
| |
| // commandButton does not need to be nested in a form since JSF 2.0 |
| UIForm form = ComponentUtils.closest(UIForm.class, uiComponent); |
| |
| boolean reset = isReset(uiComponent); |
| boolean button = isButton(uiComponent); |
| |
| Map<String, List<ClientBehavior>> behaviors = null; |
| if (uiComponent instanceof ClientBehaviorHolder) |
| { |
| behaviors = ((ClientBehaviorHolder) uiComponent).getClientBehaviors(); |
| if (!behaviors.isEmpty()) |
| { |
| ResourceUtils.renderDefaultJsfJsInlineIfNecessary(facesContext, writer); |
| } |
| } |
| |
| List<UIParameter> validParams = HtmlRendererUtils.getValidUIParameterChildren( |
| facesContext, getChildren(uiComponent), false, false); |
| |
| String commandOnclick = (String)uiComponent.getAttributes().get(HTML.ONCLICK_ATTR); |
| |
| if (commandOnclick != null && (validParams != null && !validParams.isEmpty())) |
| { |
| ResourceUtils.renderDefaultJsfJsInlineIfNecessary(facesContext, writer); |
| } |
| |
| writer.startElement(HTML.INPUT_ELEM, uiComponent); |
| |
| writer.writeAttribute(HTML.ID_ATTR, clientId, JSFAttr.ID_ATTR); |
| writer.writeAttribute(HTML.NAME_ATTR, clientId, JSFAttr.ID_ATTR); |
| |
| String image = RendererUtils.getIconSrc(facesContext, uiComponent, JSFAttr.IMAGE_ATTR); |
| if (image != null) |
| { |
| writer.writeAttribute(HTML.TYPE_ATTR, HTML.INPUT_TYPE_IMAGE, JSFAttr.TYPE_ATTR); |
| writer.writeURIAttribute(HTML.SRC_ATTR, image, JSFAttr.IMAGE_ATTR); |
| } |
| else |
| { |
| String type = getType(uiComponent); |
| |
| if (type == null || (!reset && !button)) |
| { |
| type = HTML.INPUT_TYPE_SUBMIT; |
| } |
| writer.writeAttribute(HTML.TYPE_ATTR, type, JSFAttr.TYPE_ATTR); |
| Object value = getValue(uiComponent); |
| if (value != null) |
| { |
| writer.writeAttribute(HTML.VALUE_ATTR, value, JSFAttr.VALUE_ATTR); |
| } |
| } |
| |
| if (HtmlRendererUtils.hasClientBehavior(ClientBehaviorEvents.CLICK, behaviors, facesContext) |
| || HtmlRendererUtils.hasClientBehavior(ClientBehaviorEvents.ACTION, behaviors, facesContext)) |
| { |
| if (!reset && !button) |
| { |
| String onClick = buildBehaviorizedOnClick(uiComponent, behaviors, facesContext, writer, |
| form, validParams); |
| if (onClick.length() != 0) |
| { |
| writer.writeAttribute(HTML.ONCLICK_ATTR, onClick, null); |
| } |
| } |
| else |
| { |
| Collection<ClientBehaviorContext.Parameter> paramList = |
| HtmlRendererUtils.getClientBehaviorContextParameters( |
| HtmlRendererUtils.mapAttachedParamsToStringValues(facesContext, uiComponent)); |
| |
| String onClick = HtmlRendererUtils.buildBehaviorChain(facesContext, uiComponent, |
| ClientBehaviorEvents.CLICK, paramList, ClientBehaviorEvents.ACTION, paramList, behaviors, |
| commandOnclick , null); |
| if (onClick.length() != 0) |
| { |
| writer.writeAttribute(HTML.ONCLICK_ATTR, onClick, null); |
| } |
| } |
| |
| Map<String, Object> attributes = uiComponent.getAttributes(); |
| |
| HtmlRendererUtils.buildBehaviorChain(facesContext, uiComponent, ClientBehaviorEvents.DBLCLICK, null, |
| behaviors, (String) attributes.get(HTML.ONDBLCLICK_ATTR), ""); |
| } |
| else |
| { |
| //fallback into the pre 2.0 code to keep backwards compatibility with libraries which rely on internals |
| if (!reset && !button) |
| { |
| StringBuilder onClick = buildOnClick(uiComponent, facesContext, writer, validParams); |
| if (onClick.length() != 0) |
| { |
| writer.writeAttribute(HTML.ONCLICK_ATTR, onClick.toString(), null); |
| } |
| } |
| else |
| { |
| HtmlRendererUtils.renderHTMLStringAttribute(writer, uiComponent, HTML.ONCLICK_ATTR, HTML.ONCLICK_ATTR); |
| } |
| } |
| |
| if (isCommonPropertiesOptimizationEnabled(facesContext)) |
| { |
| CommonPropertyUtils.renderButtonPassthroughPropertiesWithoutDisabledAndEvents(writer, |
| CommonPropertyUtils.getCommonPropertiesMarked(uiComponent), uiComponent); |
| } |
| else |
| { |
| HtmlRendererUtils.renderHTMLAttributes(writer, uiComponent, |
| HTML.BUTTON_PASSTHROUGH_ATTRIBUTES_WITHOUT_DISABLED_AND_EVENTS); |
| } |
| |
| if (behaviors != null && !behaviors.isEmpty()) |
| { |
| HtmlRendererUtils.renderBehaviorizedEventHandlersWithoutOnclick( |
| facesContext, writer, uiComponent, behaviors); |
| HtmlRendererUtils.renderBehaviorizedFieldEventHandlers(facesContext, writer, uiComponent, behaviors); |
| } |
| else |
| { |
| if (isCommonPropertiesOptimizationEnabled(facesContext)) |
| { |
| long commonPropertiesMarked = CommonPropertyUtils.getCommonPropertiesMarked(uiComponent); |
| CommonPropertyUtils.renderEventPropertiesWithoutOnclick(writer, commonPropertiesMarked, uiComponent); |
| CommonPropertyUtils.renderCommonFieldEventProperties(writer, commonPropertiesMarked, uiComponent); |
| } |
| else |
| { |
| HtmlRendererUtils.renderHTMLAttributes(writer, uiComponent, |
| HTML.EVENT_HANDLER_ATTRIBUTES_WITHOUT_ONCLICK); |
| HtmlRendererUtils.renderHTMLAttributes(writer, uiComponent, |
| HTML.COMMON_FIELD_EVENT_ATTRIBUTES); |
| } |
| } |
| |
| if (isDisabled(facesContext, uiComponent)) |
| { |
| writer.writeAttribute(HTML.DISABLED_ATTR, Boolean.TRUE, JSFAttr.DISABLED_ATTR); |
| } |
| |
| if (isReadonly(facesContext, uiComponent)) |
| { |
| writer.writeAttribute(HTML.READONLY_ATTR, Boolean.TRUE, JSFAttr.READONLY_ATTR); |
| } |
| } |
| |
| @Override |
| public void encodeEnd(FacesContext facesContext, UIComponent uiComponent) throws IOException |
| { |
| ResponseWriter writer = facesContext.getResponseWriter(); |
| |
| writer.endElement(HTML.INPUT_ELEM); |
| |
| UIForm form = ComponentUtils.closest(UIForm.class, uiComponent); |
| if (form != null) |
| { |
| HtmlFormRendererBase.renderScrollHiddenInputIfNecessary(form, facesContext, writer); |
| } |
| } |
| |
| protected String buildBehaviorizedOnClick(UIComponent uiComponent, Map<String, List<ClientBehavior>> behaviors, |
| FacesContext facesContext, ResponseWriter writer, |
| UIComponent form, List<UIParameter> validParams) |
| throws IOException |
| { |
| StringBuilder sb = SharedStringBuilder.get(facesContext, SB_BUILD_BEHAVIORIZED_ONCLICK); |
| |
| //user onclick part |
| String commandOnClick = (String) uiComponent.getAttributes().get(HTML.ONCLICK_ATTR); |
| if (commandOnClick != null) |
| { |
| sb.append(commandOnClick); |
| sb.append(';'); |
| } |
| String userOnClick = sb.toString(); |
| |
| // reset SB and reuse |
| sb.setLength(0); |
| |
| if (form != null) |
| { |
| // There is no clean way to detect if a "submit" behavior has been added to the component, |
| // so to keep things simple, if the button is submit type, it is responsibility of the |
| // developer to add a client behavior that submit the form, for example using a f:ajax tag. |
| // Otherwise, there will be a situation where a full submit could be trigger after an ajax |
| // operation. Note we still need to append two scripts if necessary: autoscroll and clear |
| // hidden fields, because this code is called for a submit button. |
| //if (behaviors.isEmpty() && validParams != null && !validParams.isEmpty() ) |
| //{ |
| // rendererOnClick.append(buildServerOnclick(facesContext, uiComponent, |
| // uiComponent.getClientId(facesContext), nestedFormInfo, validParams)); |
| //} |
| //else |
| //{ |
| if (JavascriptUtils.isRenderClearJavascriptOnButton(facesContext.getExternalContext())) |
| { |
| //call the script to clear the form (clearFormHiddenParams_<formName>) method |
| HtmlRendererUtils.appendClearHiddenCommandFormParamsFunctionCall(sb, |
| form.getClientId(facesContext)); |
| } |
| //} |
| } |
| |
| //according to the specification in jsf.util.chain jdocs and the spec document we have to use |
| //jsf.util.chain to chain the functions and |
| Collection<ClientBehaviorContext.Parameter> paramList = HtmlRendererUtils.getClientBehaviorContextParameters( |
| HtmlRendererUtils.mapAttachedParamsToStringValues(facesContext, uiComponent)); |
| |
| return HtmlRendererUtils.buildBehaviorChain(facesContext, uiComponent, |
| ClientBehaviorEvents.CLICK, paramList, ClientBehaviorEvents.ACTION, paramList, behaviors, |
| userOnClick , sb.toString()); |
| } |
| |
| private StringBuilder addChildParameters(FacesContext context, List<UIParameter> validParams) |
| { |
| //add child parameters |
| StringBuilder params = SharedStringBuilder.get(context, SB_ADD_CHILD_PARAMETERS); |
| params.append('['); |
| |
| for (int i = 0, size = validParams.size(); i < size; i++) |
| { |
| UIParameter param = validParams.get(i); |
| String name = param.getName(); |
| Object value = param.getValue(); |
| |
| //UIParameter is no ValueHolder, so no conversion possible - calling .toString on value.... |
| // MYFACES-1832 bad charset encoding for f:param |
| // if HTMLEncoder.encode is called, then |
| // when is called on writer.writeAttribute, encode method |
| // is called again so we have a duplicated encode call. |
| // MYFACES-2726 All '\' and "'" chars must be escaped |
| // because there will be inside "'" javascript quotes, |
| // otherwise the value will not correctly restored when |
| // the command is post. |
| //String strParamValue = value != null ? value.toString() : ""; |
| String strParamValue = ""; |
| if (value != null) |
| { |
| strParamValue = value.toString(); |
| StringBuilder buff = null; |
| for (int j = 0; j < strParamValue.length(); j++) |
| { |
| char c = strParamValue.charAt(j); |
| if (c == '\'' || c == '\\') |
| { |
| if (buff == null) |
| { |
| buff = new StringBuilder(); |
| buff.append(strParamValue.substring(0,j)); |
| } |
| buff.append('\\'); |
| buff.append(c); |
| } |
| else if (buff != null) |
| { |
| buff.append(c); |
| } |
| } |
| if (buff != null) |
| { |
| strParamValue = buff.toString(); |
| } |
| } |
| |
| if (params.length() > 1) |
| { |
| params.append(','); |
| } |
| |
| params.append("['"); |
| params.append(name); |
| params.append("','"); |
| params.append(strParamValue); |
| params.append("']"); |
| } |
| params.append(']'); |
| return params; |
| } |
| |
| private String getTarget(UIComponent component) |
| { |
| // for performance reason: double check for the target attribute |
| String target; |
| if (component instanceof HtmlCommandLink) |
| { |
| target = ((HtmlCommandLink) component).getTarget(); |
| } |
| else |
| { |
| target = (String) component.getAttributes().get(HTML.TARGET_ATTR); |
| } |
| return target; |
| } |
| |
| protected StringBuilder buildOnClick(UIComponent uiComponent, FacesContext facesContext, |
| ResponseWriter writer, List<UIParameter> validParams) |
| throws IOException |
| { |
| StringBuilder onClick = SharedStringBuilder.get(facesContext, SB_BUILD_ONCLICK); |
| String commandOnClick = (String) uiComponent.getAttributes().get(HTML.ONCLICK_ATTR); |
| |
| if (commandOnClick != null) |
| { |
| onClick.append("var cf = function(){"); |
| onClick.append(commandOnClick); |
| onClick.append('}'); |
| onClick.append(';'); |
| onClick.append("var oamSF = function(){"); |
| } |
| |
| UIForm form = ComponentUtils.closest(UIForm.class, uiComponent); |
| if (form != null) |
| { |
| if (validParams != null && !validParams.isEmpty() ) |
| { |
| StringBuilder params = addChildParameters(facesContext, validParams); |
| |
| String target = getTarget(uiComponent); |
| |
| onClick.append("return "). |
| append(HtmlRendererUtils.SUBMIT_FORM_FN_NAME_JSF2).append("('"). |
| append(form.getClientId(facesContext)).append("','"). |
| append(uiComponent.getClientId(facesContext)).append('\''); |
| |
| if (params.length() > 2 || target != null) |
| { |
| onClick.append(','). |
| append(target == null ? "null" : ('\'' + target + '\'')).append(','). |
| append(params); |
| } |
| onClick.append(");"); |
| } |
| else |
| { |
| |
| if (JavascriptUtils.isRenderClearJavascriptOnButton(facesContext.getExternalContext())) |
| { |
| //call the script to clear the form (clearFormHiddenParams_<formName>) method |
| HtmlRendererUtils.appendClearHiddenCommandFormParamsFunctionCall(onClick, |
| form.getClientId(facesContext)); |
| } |
| } |
| } |
| |
| if (commandOnClick != null) |
| { |
| onClick.append('}'); |
| onClick.append(';'); |
| onClick.append("return (cf.apply(this, [])==false)? false : oamSF.apply(this, []); "); |
| } |
| |
| return onClick; |
| } |
| |
| |
| protected boolean isDisabled(FacesContext facesContext, UIComponent uiComponent) |
| { |
| if (uiComponent instanceof HtmlCommandButton) |
| { |
| return ((HtmlCommandButton) uiComponent).isDisabled(); |
| } |
| |
| return RendererUtils.getBooleanAttribute(uiComponent, HTML.DISABLED_ATTR, false); |
| } |
| |
| protected boolean isReadonly(FacesContext facesContext, UIComponent uiComponent) |
| { |
| if (uiComponent instanceof HtmlCommandButton) |
| { |
| return ((HtmlCommandButton)uiComponent).isReadonly(); |
| } |
| return RendererUtils.getBooleanAttribute( |
| uiComponent, HTML.READONLY_ATTR, false); |
| } |
| |
| private String getType(UIComponent uiComponent) |
| { |
| if (uiComponent instanceof HtmlCommandButton) |
| { |
| return ((HtmlCommandButton)uiComponent).getType(); |
| } |
| return (String)uiComponent.getAttributes().get(JSFAttr.TYPE_ATTR); |
| } |
| |
| private Object getValue(UIComponent uiComponent) |
| { |
| if (uiComponent instanceof ValueHolder) |
| { |
| return ((ValueHolder)uiComponent).getValue(); |
| } |
| return uiComponent.getAttributes().get(JSFAttr.VALUE_ATTR); |
| } |
| } |