org.richfaces.renderkit.util.RendererUtils Maven / Gradle / Ivy
/*
* JBoss, Home of Professional Open Source
* Copyright 2013, Red Hat, Inc. and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.richfaces.renderkit.util;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import javax.faces.application.FacesMessage;
import javax.faces.application.ViewHandler;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIComponent;
import javax.faces.component.UIForm;
import javax.faces.component.UIParameter;
import javax.faces.component.UIViewRoot;
import javax.faces.component.behavior.ClientBehaviorContext.Parameter;
import javax.faces.component.behavior.ClientBehaviorHolder;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import org.ajax4jsf.Messages;
import org.ajax4jsf.component.JavaScriptParameter;
import org.ajax4jsf.javascript.JSReference;
import org.richfaces.renderkit.HtmlConstants;
import org.richfaces.renderkit.RenderKitUtils;
/**
* Util class for common render operations - render pass-through html attributes, iterate over child components etc.
*
* @author [email protected] (latest modification by $Author: alexsmirnov $)
* @version $Revision: 1.1.2.6 $ $Date: 2007/02/08 19:07:16 $
*
*/
public class RendererUtils {
public static final String DUMMY_FORM_ID = ":_form";
// we'd better use this instance multithreadly quickly
private static final RendererUtils INSTANCE = new RendererUtils();
/**
* Substitutions for components properies names and HTML attributes names.
*/
private static final Map SUBSTITUTIONS = new HashMap();
static {
SUBSTITUTIONS.put(HtmlConstants.CLASS_ATTRIBUTE, "styleClass");
Arrays.sort(HtmlConstants.PASS_THRU);
Arrays.sort(HtmlConstants.PASS_THRU_EVENTS);
Arrays.sort(HtmlConstants.PASS_THRU_BOOLEAN);
Arrays.sort(HtmlConstants.PASS_THRU_URI);
}
// can be created by subclasses;
// administratively restricted to be created by package members ;)
protected RendererUtils() {
super();
}
/**
* Use this method to get singleton instance of RendererUtils
*
* @return singleton instance
*/
public static RendererUtils getInstance() {
return INSTANCE;
}
/**
* Encode id attribute with clientId component property
*
* @param context
* @param component
* @throws IOException
*/
public void encodeId(FacesContext context, UIComponent component) throws IOException {
encodeId(context, component, HtmlConstants.ID_ATTRIBUTE);
}
/**
* Encode clientId to custom attribute ( for example, to control name )
*
* @param context
* @param component
* @param attribute
* @throws IOException
*/
public void encodeId(FacesContext context, UIComponent component, String attribute) throws IOException {
String clientId = null;
try {
clientId = component.getClientId(context);
} catch (Exception e) {
// just ignore if clientId wasn't inited yet
}
if (null != clientId) {
context.getResponseWriter().writeAttribute(attribute, clientId, (String) getComponentAttributeName(attribute));
}
}
/**
* Encode id attribute with clientId component property. Encoded only if id not auto generated.
*
* @param context
* @param component
* @throws IOException
*/
public void encodeCustomId(FacesContext context, UIComponent component) throws IOException {
if (hasExplicitId(component)) {
context.getResponseWriter().writeAttribute(HtmlConstants.ID_ATTRIBUTE, component.getClientId(context),
HtmlConstants.ID_ATTRIBUTE);
}
}
/**
* Returns value of the parameter. If parameter is instance of JavaScriptParameter
, NoEcape
* attribute is applied.
*
* @param parameter instance of UIParameter
* @return Object
parameter value
*/
public Object createParameterValue(UIParameter parameter) {
Object value = parameter.getValue();
boolean escape = true;
if (parameter instanceof JavaScriptParameter) {
JavaScriptParameter actionParam = (JavaScriptParameter) parameter;
escape = !actionParam.isNoEscape();
}
if (escape) {
if (value == null) {
value = "";
}
} else {
value = new JSReference(value.toString());
}
return value;
}
public Map createParametersMap(FacesContext context, UIComponent component) {
Map parameters = new LinkedHashMap();
if (component.getChildCount() > 0) {
for (UIComponent child : component.getChildren()) {
if (child instanceof UIParameter) {
UIParameter parameter = (UIParameter) child;
String name = parameter.getName();
Object value = createParameterValue(parameter);
if (null == name) {
throw new IllegalArgumentException(Messages.getMessage(Messages.UNNAMED_PARAMETER_ERROR,
component.getClientId(context)));
}
parameters.put(name, value);
}
}
}
return parameters;
}
private void encodeBehaviors(FacesContext context, ClientBehaviorHolder behaviorHolder, String defaultHtmlEventName,
String[] attributesExclusions) throws IOException {
// if (attributesExclusions != null && attributesExclusions.length != 0) {
// assert false : "Not supported yet";
// }
// TODO: disabled component check
String defaultEventName = behaviorHolder.getDefaultEventName();
Collection eventNames = behaviorHolder.getEventNames();
if (eventNames != null) {
UIComponent component = (UIComponent) behaviorHolder;
ResponseWriter writer = context.getResponseWriter();
Collection parametersList = HandlersChain.createParametersList(createParametersMap(context, component));
for (String behaviorEventName : eventNames) {
String htmlEventName = "on" + behaviorEventName;
if ((attributesExclusions == null) || (Arrays.binarySearch(attributesExclusions, htmlEventName) < 0)) {
HandlersChain handlersChain = new HandlersChain(context, component, parametersList);
handlersChain.addInlineHandlerFromAttribute(htmlEventName);
handlersChain.addBehaviors(behaviorEventName);
String handlerScript = handlersChain.toScript();
if (!isEmpty(handlerScript)) {
writer.writeAttribute(htmlEventName, handlerScript, htmlEventName);
}
}
}
}
}
/**
* Encode common pass-thru html attributes.
*
* @param context
* @param component
* @throws IOException
*/
public void encodePassThru(FacesContext context, UIComponent component, String defaultHtmlEvent) throws IOException {
encodeAttributesFromArray(context, component, HtmlConstants.PASS_THRU);
if (component instanceof ClientBehaviorHolder) {
ClientBehaviorHolder clientBehaviorHolder = (ClientBehaviorHolder) component;
encodeBehaviors(context, clientBehaviorHolder, defaultHtmlEvent, null);
} else {
encodeAttributesFromArray(context, component, HtmlConstants.PASS_THRU_EVENTS);
}
}
/**
* Encode pass-through attributes except specified ones
*
* @param context
* @param component
* @param exclusions
* @throws IOException
*/
public void encodePassThruWithExclusions(FacesContext context, UIComponent component, String exclusions,
String defaultHtmlEvent) throws IOException {
if (null != exclusions) {
String[] exclusionsArray = exclusions.split(",");
encodePassThruWithExclusionsArray(context, component, exclusionsArray, defaultHtmlEvent);
}
}
public void encodePassThruWithExclusionsArray(FacesContext context, UIComponent component, String[] exclusions,
String defaultHtmlEvent) throws IOException {
ResponseWriter writer = context.getResponseWriter();
Map attributes = component.getAttributes();
Arrays.sort(exclusions);
for (int i = 0; i < HtmlConstants.PASS_THRU.length; i++) {
String attribute = HtmlConstants.PASS_THRU[i];
if (Arrays.binarySearch(exclusions, attribute) < 0) {
encodePassThruAttribute(context, attributes, writer, attribute);
}
}
if (component instanceof ClientBehaviorHolder) {
ClientBehaviorHolder clientBehaviorHolder = (ClientBehaviorHolder) component;
encodeBehaviors(context, clientBehaviorHolder, defaultHtmlEvent, exclusions);
} else {
for (int i = 0; i < HtmlConstants.PASS_THRU_EVENTS.length; i++) {
String attribute = HtmlConstants.PASS_THRU_EVENTS[i];
if (Arrays.binarySearch(exclusions, attribute) < 0) {
encodePassThruAttribute(context, attributes, writer, attribute);
}
}
}
}
/**
* Encode one pass-thru attribute, with plain/boolean/url value, got from properly component attribute.
*
* @param context
* @param writer
* @param attribute
* @throws IOException
*/
public void encodePassThruAttribute(FacesContext context, Map attributes, ResponseWriter writer,
String attribute) throws IOException {
Object value = attributeValue(attribute, attributes.get(getComponentAttributeName(attribute)));
if ((null != value) && RenderKitUtils.shouldRenderAttribute(value)) {
if (Arrays.binarySearch(HtmlConstants.PASS_THRU_URI, attribute) >= 0) {
String url = context.getApplication().getViewHandler().getResourceURL(context, value.toString());
url = context.getExternalContext().encodeResourceURL(url);
writer.writeURIAttribute(attribute, url, attribute);
} else {
writer.writeAttribute(attribute, value, attribute);
}
}
}
public void encodeAttributesFromArray(FacesContext context, UIComponent component, String[] attrs) throws IOException {
ResponseWriter writer = context.getResponseWriter();
Map attributes = component.getAttributes();
for (int i = 0; i < attrs.length; i++) {
String attribute = attrs[i];
encodePassThruAttribute(context, attributes, writer, attribute);
}
}
/**
* Encode attributes given by comma-separated string list.
*
* @param context current JSF context
* @param component for with render attributes values
* @param attrs comma separated list of attributes
* @throws IOException
*/
public void encodeAttributes(FacesContext context, UIComponent component, String attrs) throws IOException {
if (null != attrs) {
String[] attrsArray = attrs.split(",");
encodeAttributesFromArray(context, component, attrsArray);
}
}
/**
* @param context
* @param component
* @param property
* @param attributeName
*
* @throws IOException
*/
public void encodeAttribute(FacesContext context, UIComponent component, Object property, String attributeName)
throws IOException {
ResponseWriter writer = context.getResponseWriter();
Object value = component.getAttributes().get(property);
if (RenderKitUtils.shouldRenderAttribute(value)) {
writer.writeAttribute(attributeName, value, property.toString());
}
}
public void encodeAttribute(FacesContext context, UIComponent component, String attribute) throws IOException {
encodeAttribute(context, component, getComponentAttributeName(attribute), attribute);
}
/**
* Checks if the argument passed in is empty or not. Object is empty if it is:
* - null
* - zero-length string
* - empty collection
* - empty map
* - zero-length array
*
* @param o object to check for emptiness
* @since 3.3.2
* @return true
if the argument is empty, false
otherwise
*/
public boolean isEmpty(Object o) {
if (null == o) {
return true;
}
if (o instanceof String) {
return 0 == ((String) o).length();
}
if (o instanceof Collection>) {
return ((Collection>) o).isEmpty();
}
if (o instanceof Map, ?>) {
return ((Map, ?>) o).isEmpty();
}
if (o.getClass().isArray()) {
return Array.getLength(o) == 0;
}
return false;
}
/**
* Convert HTML attribute name to component property name.
*
* @param key
* @return
*/
protected Object getComponentAttributeName(Object key) {
Object converted = SUBSTITUTIONS.get(key);
if (null == converted) {
return key;
} else {
return converted;
}
}
/**
* Convert attribute value to proper object. For known html boolean attributes return name for true value, otherthise -
* null. For non-boolean attributes return same value.
*
* @param name attribute name.
* @param value
* @return
*/
protected Object attributeValue(String name, Object value) {
if (null == value || Arrays.binarySearch(HtmlConstants.PASS_THRU_BOOLEAN, name) < 0) {
return value;
}
boolean checked;
if (value instanceof Boolean) {
checked = ((Boolean) value).booleanValue();
} else {
checked = Boolean.parseBoolean(value.toString());
}
return checked ? name : null;
}
/**
* Get boolean value of logical attribute
*
* @param component
* @param name attribute name
* @return true if attribute is equals Boolean.TRUE or String "true" , false otherwise.
*/
public boolean isBooleanAttribute(UIComponent component, String name) {
Object attrValue = component.getAttributes().get(name);
boolean result = false;
if (null != attrValue) {
if (attrValue instanceof String) {
result = "true".equalsIgnoreCase((String) attrValue);
} else {
result = Boolean.TRUE.equals(attrValue);
}
}
return result;
}
public String encodePx(String value) {
return HtmlDimensions.formatPx(HtmlDimensions.decode(value));
}
/**
* formats given value to
*
* @param value
*
* @return
*/
public String encodePctOrPx(String value) {
if (value.indexOf('%') > 0) {
return value;
} else {
return encodePx(value);
}
}
/**
* Find nested form for given component
*
* Deprecated: use {@link #getNestingForm(UIComponent)} instead
*
* @param component
* @return nested UIForm
component, or null
*/
public UIComponent getNestingForm(UIComponent component) {
return getNestingForm(null, component);
}
/**
* Find nested form for given component
*
* @param component
* @return nested UIForm
component, or null
*/
@Deprecated
public UIComponent getNestingForm(FacesContext context, UIComponent component) {
UIComponent parent = component;
// Search enclosed UIForm or ADF UIXForm component
while ((parent != null) && !(parent instanceof UIForm)
&& !("org.apache.myfaces.trinidad.Form".equals(parent.getFamily()))
&& !("oracle.adf.Form".equals(parent.getFamily()))) {
parent = parent.getParent();
}
return parent;
}
/**
* Detects whether given component is form
*/
public boolean isForm(UIComponent component) {
return component instanceof UIForm || "org.apache.myfaces.trinidad.Form".equals(component.getFamily()) || "oracle.adf.Form".equals(component.getFamily());
}
/**
* Find submitted form for given context
*
* @param facesContext
* @return submitted UIForm
component, or null
*/
public UIComponent getSubmittedForm(FacesContext facesContext) {
if (!facesContext.isPostback()) {
return null;
}
for (Entry entry : facesContext.getExternalContext().getRequestParameterMap().entrySet()) {
final String name = entry.getKey();
final String value = entry.getValue();
// form's name equals to its value
if (isFormValueSubmitted(name, value)) {
// in that case, name is equal to clientId
UIComponent component = findComponentFor(facesContext.getViewRoot(), name);
UIComponent form = getNestingForm(component);
return form;
}
}
facesContext.addMessage(null, new FacesMessage("The form wasn't detected for the request",
"The form wasn't detected for the request - rendering does not have to behave well"));
return null;
}
/**
* Determines whenever given form has been submitted
*/
public boolean isFormSubmitted(FacesContext context, UIForm form) {
if (form != null) {
String clientId = form.getClientId(context);
String formRequestParam = context.getExternalContext().getRequestParameterMap().get(clientId);
return isFormValueSubmitted(clientId, formRequestParam);
}
return false;
}
/**
* Determines if a form was submitted based on its clientId (which equals to request parameter name) and submitted value
*
* @return true if clientId and value equals and they are not null; false otherwise
*/
private boolean isFormValueSubmitted(String clientId, String value) {
if (clientId == null) {
return false;
}
return clientId.equals(value);
}
/**
* @param context
* @param component
* @throws IOException
*/
public void encodeBeginFormIfNessesary(FacesContext context, UIComponent component) throws IOException {
UIComponent form = getNestingForm(context, component);
if (null == form) {
ResponseWriter writer = context.getResponseWriter();
String clientId = component.getClientId(context) + DUMMY_FORM_ID;
encodeBeginForm(context, component, writer, clientId);
// writer.writeAttribute(HTML.STYLE_ATTRIBUTE, "margin:0;
// padding:0;", null);
}
}
/**
* @param context
* @param component
* @param writer
* @param clientId
* @throws IOException
*/
public void encodeBeginForm(FacesContext context, UIComponent component, ResponseWriter writer, String clientId)
throws IOException {
String actionURL = getActionUrl(context);
String encodeActionURL = context.getExternalContext().encodeActionURL(actionURL);
writer.startElement(HtmlConstants.FORM_ELEMENT, component);
writer.writeAttribute(HtmlConstants.ID_ATTRIBUTE, clientId, null);
writer.writeAttribute(HtmlConstants.METHOD_ATTRIBUTE, "post", null);
writer.writeAttribute(HtmlConstants.STYLE_ATTRIBUTE, "margin:0; padding:0; display: inline;", null);
writer.writeURIAttribute(HtmlConstants.ACTION_ATTRIBUTE, encodeActionURL, "action");
}
/**
* @param context
* @param component
* @throws IOException
*/
public void encodeEndFormIfNessesary(FacesContext context, UIComponent component) throws IOException {
UIComponent form = getNestingForm(context, component);
if (null == form) {
ResponseWriter writer = context.getResponseWriter();
// TODO - hidden form parameters ?
encodeEndForm(context, writer);
}
}
/**
* Write state saving markers to context, include MyFaces view sequence.
*
* @param context
* @throws IOException
*/
public static void writeState(FacesContext context) throws IOException {
context.getApplication().getViewHandler().writeState(context);
}
/**
* @param context
* @param writer
* @throws IOException
*/
public void encodeEndForm(FacesContext context, ResponseWriter writer) throws IOException {
UIViewRoot viewRoot = context.getViewRoot();
for (UIComponent resource : viewRoot.getComponentResources(context, "form")) {
resource.encodeAll(context);
}
writeState(context);
writer.endElement(HtmlConstants.FORM_ELEMENT);
}
/**
* @param facesContext
* @return String A String representing the action URL
*/
public String getActionUrl(FacesContext facesContext) {
ViewHandler viewHandler = facesContext.getApplication().getViewHandler();
String viewId = facesContext.getViewRoot().getViewId();
return viewHandler.getActionURL(facesContext, viewId);
}
/**
* Simplified version of {@link encodeId(FacesContext, UIComponent)}
*
* @param context
* @param component
* @return client id of current component
*/
public String clientId(FacesContext context, UIComponent component) {
String clientId = "";
try {
clientId = component.getClientId(context);
} catch (Exception e) {
// just ignore
}
return clientId;
}
/**
* Write JavaScript with start/end elements and type.
*
* @param context
* @param component
* @param script
*/
public void writeScript(FacesContext context, UIComponent component, Object script) throws IOException {
ResponseWriter writer = context.getResponseWriter();
writer.startElement(HtmlConstants.SCRIPT_ELEM, component);
writer.writeAttribute(HtmlConstants.TYPE_ATTR, "text/javascript", "type");
writer.writeText(script, null);
writer.endElement(HtmlConstants.SCRIPT_ELEM);
}
/**
* If target component contains generated id and for doesn't, correct for id
*
* @param forAttr
* @param component
*
*/
public String correctForIdReference(String forAttr, UIComponent component) {
int contains = forAttr.indexOf(UIViewRoot.UNIQUE_ID_PREFIX);
if (contains <= 0) {
String id = component.getId();
int pos = id.indexOf(UIViewRoot.UNIQUE_ID_PREFIX);
if (pos > 0) {
return forAttr.concat(id.substring(pos));
}
}
return forAttr;
}
public void encodeChildren(FacesContext context, UIComponent component) throws IOException {
if (component.getChildCount() > 0) {
for (UIComponent child : component.getChildren()) {
child.encodeAll(context);
}
}
}
public boolean hasExplicitId(UIComponent component) {
return component.getId() != null && !component.getId().startsWith(UIViewRoot.UNIQUE_ID_PREFIX);
}
/**
* Deprecated, use {@link #findComponentFor(UIComponent, String)}.
*/
@Deprecated
public UIComponent findComponentFor(FacesContext context, UIComponent component, String id) {
return findComponentFor(component, id);
}
/**
* A modified JSF alghoritm for looking up components.
*
* First try to find the component with given ID in subtree and then lookup in parents' subtrees.
*
* If no component is found this way, it uses {@link #findUIComponentBelow(UIComponent, String)} applied to root component.
*
* @param component
* @param id
* @return
*/
public UIComponent findComponentFor(UIComponent component, String id) {
if (id == null) {
throw new NullPointerException("id is null!");
}
if (id.length() == 0) {
return null;
}
UIComponent target = null;
UIComponent parent = component;
UIComponent root = component;
while ((null == target) && (null != parent)) {
target = parent.findComponent(id);
root = parent;
parent = parent.getParent();
}
if (null == target) {
target = findUIComponentBelow(root, id);
}
return target;
}
/**
* Looks up component with given ID in subtree of given component including all component's chilren, component's facets and subtrees under naming containers.
*/
private UIComponent findUIComponentBelow(UIComponent root, String id) {
UIComponent target = null;
for (Iterator iter = root.getFacetsAndChildren(); iter.hasNext();) {
UIComponent child = (UIComponent) iter.next();
if (child instanceof NamingContainer) {
try {
target = child.findComponent(id);
} catch (IllegalArgumentException iae) {
continue;
}
}
if (target == null) {
if ((child.getChildCount() > 0) || (child.getFacetCount() > 0)) {
target = findUIComponentBelow(child, id);
}
}
if (target != null) {
break;
}
}
return target;
}
}