/**
* Copyright 2014-2017 Riccardo Massera (TheCoder4.Eu).
*
* This file is part of BootsFaces.
*
* Licensed 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 net.bootsfaces.render;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.List;
import java.util.Map;
import javax.el.ValueExpression;
import javax.faces.FacesException;
import javax.faces.application.ProjectStage;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.UIComponent;
import javax.faces.component.UIForm;
import javax.faces.component.UIInput;
import javax.faces.component.ValueHolder;
import javax.faces.component.behavior.ClientBehavior;
import javax.faces.component.behavior.ClientBehaviorHolder;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;
import javax.faces.render.Renderer;
import net.bootsfaces.beans.ELTools;
import net.bootsfaces.component.ajax.AJAXRenderer;
import net.bootsfaces.component.form.Form;
import net.bootsfaces.utils.BsfUtils;
import net.bootsfaces.utils.FacesMessages;
public class CoreRenderer extends Renderer {
/**
* Method that provide ability to render pass through attributes.
*
* @param context
* @param component
* @param attrs
* @throws IOException
*/
protected void renderPassThruAttributes(FacesContext context, UIComponent component, String[] attrs,
boolean shouldRenderDataAttributes) throws IOException {
ResponseWriter writer = context.getResponseWriter();
if ((attrs == null || attrs.length <= 0) && shouldRenderDataAttributes == false)
return;
// pre-defined attributes
for (String attribute : component.getAttributes().keySet()) {
boolean attributeToRender = false;
if (shouldRenderDataAttributes && attribute.startsWith("data-")) {
attributeToRender = true;
}
if (!attributeToRender && attrs != null) {
for (String ca : attrs) {
if (attribute.equals(ca)) {
attributeToRender = true;
break;
}
}
}
if (attributeToRender) {
Object value = component.getAttributes().get(attribute);
if (shouldRenderAttribute(value))
writer.writeAttribute(attribute, value.toString(), attribute);
}
}
}
protected void renderPassThruAttributes(FacesContext context, UIComponent component, String[] attrs)
throws IOException {
ResponseWriter writer = context.getResponseWriter();
// pre-defined attributes
if (attrs != null && attrs.length > 0) {
for (String attribute : attrs) {
Object value = component.getAttributes().get(attribute);
if (shouldRenderAttribute(value))
writer.writeAttribute(attribute, value.toString(), attribute);
}
}
}
/**
* @deprecated Use {@link CoreRenderer#generateErrorAndRequiredClass(javax.faces.component.UIInput, javax.faces.context.ResponseWriter, java.lang.String, java.lang.String, java.lang.String, java.lang.String) } instead
*
* Renders the CSS pseudo classes for required fields and for the error
* levels.
*
* @param input
* @param rw
* @param clientId
* @throws IOException
*/
@Deprecated
public void generateErrorAndRequiredClassForLabels(UIInput input, ResponseWriter rw, String clientId,
String additionalClass) throws IOException {
String styleClass = getErrorAndRequiredClass(input, clientId);
if (null != additionalClass) {
additionalClass = additionalClass.trim();
if (additionalClass.trim().length() > 0) {
styleClass += " " + additionalClass;
}
}
UIForm currentForm = AJAXRenderer.getSurroundingForm((UIComponent) input, true);
if (currentForm instanceof Form) {
if (((Form) currentForm).isHorizontal()) {
styleClass += " control-label";
}
}
if (input instanceof IResponsiveLabel) {
String responsiveLabelClass = Responsive.getResponsiveLabelClass((IResponsiveLabel) input);
if (null != responsiveLabelClass) {
styleClass += " " + responsiveLabelClass;
}
}
rw.writeAttribute("class", styleClass, "class");
}
/**
* Renders the CSS pseudo classes for required fields and for the error
* levels.
*
* @param input
* @param rw
* @param clientId
* @throws IOException
*/
protected void generateErrorAndRequiredClass(UIInput input, ResponseWriter rw, String clientId,
String additionalClass1, String additionalClass2, String additionalClass3) throws IOException {
String styleClass = getErrorAndRequiredClass(input, clientId);
if (null != additionalClass1) {
additionalClass1 = additionalClass1.trim();
if (additionalClass1.trim().length() > 0) {
styleClass += " " + additionalClass1;
}
}
if (null != additionalClass2) {
additionalClass2 = additionalClass2.trim();
if (additionalClass2.trim().length() > 0) {
styleClass += " " + additionalClass2;
}
}
if (null != additionalClass3) {
additionalClass3 = additionalClass3.trim();
if (additionalClass3.trim().length() > 0) {
styleClass += " " + additionalClass3;
}
}
rw.writeAttribute("class", styleClass, "class");
}
/**
* Yields the value of the required and error level CSS class.
*
* @param input must not be null
* @param clientId must not be null
* @return can never be null
*/
public String getErrorAndRequiredClass(UIInput input, String clientId) {
String styleClass = "";
if (BsfUtils.isLegacyFeedbackClassesEnabled()) {
styleClass = FacesMessages.getErrorSeverityClass(clientId);
}
if (input.isRequired()) {
styleClass += " bf-required";
} else {
Annotation[] readAnnotations = ELTools.readAnnotations(input);
if (null != readAnnotations && readAnnotations.length > 0) {
for (Annotation a : readAnnotations) {
if ((a.annotationType().getName().endsWith("NotNull"))
|| (a.annotationType().getName().endsWith("NotEmpty"))
|| (a.annotationType().getName().endsWith("NotBlank"))) {
styleClass += " bf-required";
break;
}
}
}
}
return styleClass;
}
protected boolean shouldRenderAttribute(Object value) {
if (value == null)
return false;
if (value instanceof Boolean) {
return ((Boolean) value);
} else if (value instanceof Number) {
Number number = (Number) value;
if (value instanceof Integer)
return number.intValue() != Integer.MIN_VALUE;
else if (value instanceof Double)
return number.doubleValue() != Double.MIN_VALUE;
else if (value instanceof Long)
return number.longValue() != Long.MIN_VALUE;
else if (value instanceof Byte)
return number.byteValue() != Byte.MIN_VALUE;
else if (value instanceof Float)
return number.floatValue() != Float.MIN_VALUE;
else if (value instanceof Short)
return number.shortValue() != Short.MIN_VALUE;
}
return true;
}
protected void decodeBehaviors(FacesContext context, UIComponent component) {
if (!(component instanceof ClientBehaviorHolder)) {
return;
}
Map<String, List<ClientBehavior>> behaviors = ((ClientBehaviorHolder) component).getClientBehaviors();
if (behaviors.isEmpty()) {
return;
}
Map<String, String> params = context.getExternalContext().getRequestParameterMap();
String behaviorEvent = params.get("javax.faces.behavior.event");
if (null != behaviorEvent) {
List<ClientBehavior> behaviorsForEvent = behaviors.get(behaviorEvent);
if (behaviorsForEvent != null && !behaviorsForEvent.isEmpty()) {
String behaviorSource = params.get("javax.faces.source");
String clientId = component.getClientId(context);
if (behaviorSource != null && clientId.equals(behaviorSource)) {
for (ClientBehavior behavior : behaviorsForEvent) {
behavior.decode(context, component);
}
}
}
}
}
public boolean componentIsDisabledOrReadonly(UIComponent component) {
return Boolean.valueOf(String.valueOf(component.getAttributes().get("disabled")))
|| Boolean.valueOf(String.valueOf(component.getAttributes().get("readonly")));
}
protected String escapeClientId(String clientId) {
if (clientId == null) {
return null;
}
// replace colons by underscores to avoid problems with jQuery
return clientId.replace(':', '_');
}
/**
* @param rw
* ResponseWriter to be used
* @param name
* Attribute name to be added
* @param value
* Attribute value to be added
* @param property
* Name of the property or attribute (if any) of the
* {@link UIComponent} associated with the containing element, to
* which this generated attribute corresponds
* @throws IllegalStateException
* if this method is called when there is no currently open
* element
* @throws IOException
* if an input/output error occurs
* @throws NullPointerException
* if <code>name</code> is <code>null</code>
*/
protected void writeAttribute(ResponseWriter rw, String name, Object value, String property) throws IOException {
if (value == null) {
return;
}
if (value instanceof String)
if (((String) value).length() == 0)
return;
rw.writeAttribute(name, value, property);
}
/**
* @param rw
* ResponseWriter to be used
* @param name
* Attribute name to be added
* @param value
* Attribute value to be added
* @throws IllegalStateException
* if this method is called when there is no currently open
* element
* @throws IOException
* if an input/output error occurs
* @throws NullPointerException
* if <code>name</code> is <code>null</code>
*/
protected void writeAttribute(ResponseWriter rw, String name, Object value) throws IOException {
if (value == null) {
return;
}
if (value instanceof String)
if (((String) value).length() == 0)
return;
rw.writeAttribute(name, value, name);
}
/**
* @param rw
* ResponseWriter to be used
* @param text
* Text to be written
* @param property
* Name of the property or attribute (if any) of the
* {@link UIComponent} associated with the containing element, to
* which this generated text corresponds
* @throws IOException
* if an input/output error occurs
* @throws NullPointerException
* if <code>text</code> is <code>null</code>
*/
public void writeText(ResponseWriter rw, Object text, String property) throws IOException {
if (text == null || text.equals("")) {
return;
}
rw.writeText(text, property);
}
/**
* @param rw
* ResponseWriter to be used
* @param text
* Text to be written
* @param component
* The {@link UIComponent} (if any) to which this element
* corresponds
* @param property
* Name of the property or attribute (if any) of the
* {@link UIComponent} associated with the containing element, to
* which this generated text corresponds
* @throws IOException
* if an input/output error occurs
* @throws NullPointerException
* if <code>text</code> is <code>null</code>
*/
public void writeText(ResponseWriter rw, Object text, UIComponent component, String property) throws IOException {
if (text == null || text.equals("")) {
return;
}
rw.writeText(text, property);
}
/**
* @param rw
* ResponseWriter to be used
* @param text
* Text to be written
* @param off
* Starting offset (zero-relative)
* @param len
* Number of characters to be written
* @throws IndexOutOfBoundsException
* if the calculated starting or ending position is outside the
* bounds of the character array
* @throws IOException
* if an input/output error occurs
* @throws NullPointerException
* if <code>text</code> is <code>null</code>
*/
public void writeText(ResponseWriter rw, char text[], int off, int len) throws IOException {
if (text == null || text.length <= 0 || "".equals(String.valueOf(text))) {
return;
}
rw.writeText(text, off, len);
}
protected void assertComponentIsInsideForm(UIComponent component, String msg) {
if (!FacesContext.getCurrentInstance().isProjectStage(ProjectStage.Production)) {
UIComponent c = component;
while ((c != null) && (!(c instanceof UIForm))) {
c = c.getParent();
}
if (!(c instanceof UIForm)) {
System.out.println("Warning: The BootsFaces component " + component.getClass()
+ " works better if put inside a form. These capabilities get lost if not put in a form:");
System.out.println(msg);
}
}
}
protected static UIForm getSurroundingForm(UIComponent component, boolean lenient) {
UIComponent c = component;
while ((c != null) && (!(c instanceof UIForm)) && (!(c instanceof Form))) {
c = c.getParent();
}
if (!(c instanceof UIForm || c instanceof Form)) {
if (lenient) {
return null;
} else {
throw new FacesException(
"The component with the id " + component.getClientId() + " must be inside a form");
}
}
return (UIForm) c;
}
public static boolean isHorizontalForm(UIComponent component) {
UIForm c = getSurroundingForm(component, true);
if (null != c && c instanceof Form) {
return ((Form) c).isHorizontal();
}
return false;
}
/**
* Algorithm works as follows; - If it's an input component, submitted value
* is checked first since it'd be the value to be used in case validation
* errors terminates jsf lifecycle - Finally the value of the component is
* retrieved from backing bean and if there's a converter, converted value
* is returned
*
* @param fc
* FacesContext instance
* @param c
* UIComponent instance whose value will be returned
* @return End text
*/
public String getValue2Render(FacesContext fc, UIComponent c) {
if (c instanceof ValueHolder) {
if (c instanceof EditableValueHolder) {
Object sv = ((EditableValueHolder) c).getSubmittedValue();
if (sv != null) {
return sv.toString();
}
}
ValueHolder vh = (ValueHolder) c;
Object val = vh.getValue();
// format the value as string
if (val != null) {
/*
* OLD Converter converter = getConverter(fc, vh);
*/
/* NEW */
Converter converter = vh.getConverter();
if (converter == null) {
Class<?> valueType = val.getClass();
if (valueType == String.class && (null == fc.getApplication().createConverter(String.class))) {
return (String) val;
}
converter = fc.getApplication().createConverter(valueType);
}
/* END NEW */
if (converter != null)
return converter.getAsString(fc, c, val);
else
return val.toString(); // Use toString as a fallback if
// there is no explicit or implicit
// converter
} else {
// component is a value holder but has no value
return null;
}
}
// component it not a value holder
return null;
}
/**
* Finds the appropriate converter for a given value holder
*
* @param fc
* FacesContext instance
* @param vh
* ValueHolder instance to look converter for
* @return Converter
*/
public static Converter getConverter(FacesContext fc, ValueHolder vh) {
// explicit converter
Converter converter = vh.getConverter();
// try to find implicit converter
if (converter == null) {
ValueExpression expr = ((UIComponent) vh).getValueExpression("value");
if (expr != null) {
Class<?> valueType = expr.getType(fc.getELContext());
if (valueType != null) {
converter = fc.getApplication().createConverter(valueType);
}
}
}
return converter;
}
/**
* This method is called by the JSF framework to get the type-safe value of
* the attribute. Do not delete this method.
*/
@Override
public Object getConvertedValue(FacesContext fc, UIComponent c, Object sval) throws ConverterException {
Converter cnv = resolveConverter(fc, c);
if (cnv != null) {
if (sval == null || sval instanceof String) {
return cnv.getAsObject(fc, c, (String)sval);
} else {
return cnv.getAsObject(fc, c, String.valueOf(sval));
}
} else {
return sval;
}
}
protected Converter resolveConverter(FacesContext context, UIComponent c) {
if (!(c instanceof ValueHolder)) {
return null;
}
Converter cnv = ((ValueHolder) c).getConverter();
if (cnv != null) {
return cnv;
} else {
ValueExpression ve = c.getValueExpression("value");
if (ve != null) {
Class<?> valType = ve.getType(context.getELContext());
if (valType != null) {
return context.getApplication().createConverter(valType);
}
}
return null;
}
}
public static void endDisabledFieldset(IContentDisabled component, ResponseWriter rw) throws IOException {
if (component.isContentDisabled()) {
rw.endElement("fieldset");
}
}
/**
* Renders the code disabling every input field and every button within a container.
* @param component
* @param rw
* @return true if an element has been rendered
* @throws IOException
*/
public static boolean beginDisabledFieldset(IContentDisabled component, ResponseWriter rw) throws IOException {
if (component.isContentDisabled()) {
rw.startElement("fieldset", (UIComponent)component);
rw.writeAttribute("disabled", "disabled", "null");
return true;
}
return false;
}
/**
* Get the main field container
*
* @deprecated Use {@link CoreInputRenderer#getWithFeedback(net.bootsfaces.render.CoreInputRenderer.InputMode, javax.faces.component.UIComponent)} instead
*
* @param additionalClass
* @param clientId
* @return
*/
@Deprecated
protected String getFormGroupWithFeedback(String additionalClass, String clientId) {
if (BsfUtils.isLegacyFeedbackClassesEnabled()) {
return additionalClass;
}
return additionalClass + " " + FacesMessages.getErrorSeverityClass(clientId);
}
protected void beginResponsiveWrapper(UIComponent component, ResponseWriter responseWriter) throws IOException {
if(!(component instanceof IResponsive)) {
return;
}
String responsiveStyleClass = Responsive.getResponsiveStyleClass((IResponsive)component, false);
if(!"".equals(responsiveStyleClass)) {
responseWriter.startElement("div", component);
responseWriter.writeAttribute("class", responsiveStyleClass, null);
}
}
protected void endResponsiveWrapper(UIComponent component, ResponseWriter responseWriter) throws IOException {
if(!(component instanceof IResponsive)) {
return;
}
String responsiveStyleClass = Responsive.getResponsiveStyleClass((IResponsive)component, false);
if(!"".equals(responsiveStyleClass)) {
responseWriter.endElement("div");
}
}
}