org.primefaces.component.inputnumber.InputNumberRenderer Maven / Gradle / Ivy
/*
* The MIT License
*
* Copyright (c) 2009-2023 PrimeTek Informatics
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.primefaces.component.inputnumber;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import javax.el.PropertyNotFoundException;
import javax.el.ValueExpression;
import javax.faces.FacesException;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.convert.ConverterException;
import org.primefaces.component.inputtext.InputText;
import org.primefaces.renderkit.InputRenderer;
import org.primefaces.util.*;
import org.primefaces.validate.bean.NegativeClientValidationConstraint;
import org.primefaces.validate.bean.PositiveClientValidationConstraint;
public class InputNumberRenderer extends InputRenderer {
// Default values for "minValue"/"maxValue" properties of the AutoNumeric Plugin
private static final BigDecimal DEFAULT_MIN_VALUE = new BigDecimal("-10000000000000");
private static final BigDecimal DEFAULT_MAX_VALUE = new BigDecimal("10000000000000");
@Override
public Object getConvertedValue(FacesContext context, UIComponent component, Object submittedValue)
throws ConverterException {
return ComponentUtils.getConvertedValue(context, component, submittedValue);
}
@Override
public void decode(FacesContext context, UIComponent component) {
InputNumber inputNumber = (InputNumber) component;
if (!shouldDecode(inputNumber)) {
return;
}
decodeBehaviors(context, inputNumber);
String inputId = inputNumber.getClientId(context) + "_hinput";
String submittedValue = context.getExternalContext().getRequestParameterMap().get(inputId);
if (submittedValue == null) {
return;
}
try {
if (LangUtils.isBlank(submittedValue)) {
Class> type = getTypeFromValueExpression(context, inputNumber);
if (type != null && type.isPrimitive() && LangUtils.isNotBlank(inputNumber.getMinValue())) {
// avoid coercion of null or empty string to 0 which may be out of [minValue, maxValue] range
submittedValue = String.valueOf(new BigDecimal(inputNumber.getMinValue()).doubleValue());
}
else if (type != null && type.isPrimitive() && LangUtils.isNotBlank(inputNumber.getMaxValue())) {
// avoid coercion of null or empty string to 0 which may be out of [minValue, maxValue] range
submittedValue = String.valueOf(new BigDecimal(inputNumber.getMaxValue()).doubleValue());
}
}
else {
// Coerce submittedValue to (effective) range of [minValue, maxValue]
BigDecimal value = new BigDecimal(submittedValue);
submittedValue = coerceValueInRange(value, inputNumber).toString();
}
}
catch (NumberFormatException ex) {
throw new FacesException("Invalid number", ex);
}
inputNumber.setSubmittedValue(submittedValue);
}
@Override
public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
InputNumber inputNumber = (InputNumber) component;
Object value = inputNumber.getValue();
String valueToRender = ComponentUtils.getValueToRender(context, inputNumber, value);
if (isValueBlank(valueToRender)) {
valueToRender = "";
}
else {
// Rendered value must always be inside the effective interval [minValue, maxValue],
// or else AutoNumeric will throw an error and the component will be broken
BigDecimal decimalToRender;
try {
decimalToRender = new BigDecimal(valueToRender);
}
catch (Exception e) {
throw new IllegalArgumentException("Error converting [" + valueToRender + "] to a decimal value;", e);
}
valueToRender = formatForPlugin(coerceValueInRange(decimalToRender, inputNumber));
}
encodeMarkup(context, inputNumber, value, valueToRender);
encodeScript(context, inputNumber, value, valueToRender);
}
protected void encodeMarkup(FacesContext context, InputNumber inputNumber, Object value, String valueToRender)
throws IOException {
ResponseWriter writer = context.getResponseWriter();
String clientId = inputNumber.getClientId(context);
String styleClass = inputNumber.getStyleClass();
styleClass = styleClass == null ? InputNumber.STYLE_CLASS : InputNumber.STYLE_CLASS + " " + styleClass;
styleClass = inputNumber.isValid() ? styleClass : styleClass + " ui-state-error"; // see #3706
writer.startElement("span", inputNumber);
writer.writeAttribute("id", clientId, null);
writer.writeAttribute("class", styleClass, "styleClass");
if (inputNumber.getStyle() != null) {
writer.writeAttribute("style", inputNumber.getStyle(), "style");
}
encodeInput(context, inputNumber, clientId, valueToRender);
encodeHiddenInput(context, inputNumber, clientId, valueToRender);
writer.endElement("span");
}
protected void encodeHiddenInput(FacesContext context, InputNumber inputNumber, String clientId, String valueToRender) throws IOException {
ResponseWriter writer = context.getResponseWriter();
String inputId = clientId + "_hinput";
writer.startElement("input", null);
writer.writeAttribute("id", inputId, null);
writer.writeAttribute("name", inputId, null);
writer.writeAttribute("type", "hidden", null);
writer.writeAttribute("autocomplete", "off", null);
writer.writeAttribute("value", valueToRender, null);
if (inputNumber.getOnchange() != null) {
writer.writeAttribute("onchange", inputNumber.getOnchange(), null);
}
if (inputNumber.getOnkeydown() != null) {
writer.writeAttribute("onkeydown", inputNumber.getOnkeydown(), null);
}
if (inputNumber.getOnkeyup() != null) {
writer.writeAttribute("onkeyup", inputNumber.getOnkeyup(), null);
}
renderValidationMetadata(context, inputNumber);
writer.endElement("input");
}
protected void encodeInput(FacesContext context, InputNumber inputNumber, String clientId, String valueToRender)
throws IOException {
ResponseWriter writer = context.getResponseWriter();
String inputId = clientId + "_input";
String inputStyle = inputNumber.getInputStyle();
String style = inputStyle;
String styleClass = createStyleClass(inputNumber, InputNumber.PropertyKeys.inputStyleClass.name(), InputText.STYLE_CLASS) ;
String inputMode = inputNumber.getInputmode();
if (inputMode == null) {
boolean isIntegral = isIntegral(context, inputNumber, inputNumber.getValue());
String decimalPlaces = getDecimalPlaces(isIntegral, inputNumber);
inputMode = "0".equals(decimalPlaces) ? "numeric" : "decimal";
inputNumber.setInputmode(inputMode);
}
writer.startElement("input", null);
writer.writeAttribute("id", inputId, null);
writer.writeAttribute("name", inputId, null);
writer.writeAttribute("type", inputNumber.getType(), null);
writer.writeAttribute("autocomplete", "off", null);
writer.writeAttribute("value", valueToRender, null);
if (!isValueBlank(style)) {
writer.writeAttribute("style", style, null);
}
writer.writeAttribute("class", styleClass, null);
renderAccessibilityAttributes(context, inputNumber);
renderPassThruAttributes(context, inputNumber, HTML.INPUT_TEXT_ATTRS_WITHOUT_EVENTS);
renderDomEvents(context, inputNumber, HTML.INPUT_TEXT_EVENTS);
renderValidationMetadata(context, inputNumber);
writer.endElement("input");
}
protected void encodeScript(FacesContext context, InputNumber inputNumber, Object value, String valueToRender)
throws IOException {
String emptyValue = isValueBlank(inputNumber.getEmptyValue()) || "empty".equalsIgnoreCase(inputNumber.getEmptyValue())
? "null"
: inputNumber.getEmptyValue();
String digitGroupSeparator = isValueBlank(inputNumber.getThousandSeparator())
? Constants.EMPTY_STRING
: inputNumber.getThousandSeparator();
String decimalSeparator = isValueBlank(inputNumber.getDecimalSeparator())
? "."
: inputNumber.getDecimalSeparator();
boolean isIntegral = isIntegral(context, inputNumber, value);
WidgetBuilder wb = getWidgetBuilder(context);
wb.init(InputNumber.class.getSimpleName(), inputNumber);
wb.attr("disabled", inputNumber.isDisabled())
.attr("valueToRender", valueToRender)
.attr("decimalCharacter", decimalSeparator, ".")
.attr("decimalCharacterAlternative", inputNumber.getDecimalSeparatorAlternative(), null)
.attr("digitGroupSeparator", digitGroupSeparator, ",")
.attr("currencySymbol", inputNumber.getSymbol())
.attr("currencySymbolPlacement", inputNumber.getSymbolPosition(), "p")
.attr("negativePositiveSignPlacement", inputNumber.getSignPosition(), null)
.attr("minimumValue", getMinimum(isIntegral, inputNumber))
.attr("maximumValue", getMaximum(isIntegral, inputNumber))
.attr("decimalPlaces", getDecimalPlaces(isIntegral, inputNumber))
.attr("emptyInputBehavior", emptyValue, "focus")
.attr("leadingZero", inputNumber.getLeadingZero(), "deny")
.attr("allowDecimalPadding", inputNumber.isPadControl(), true)
.attr("modifyValueOnWheel", inputNumber.isModifyValueOnWheel(), true)
.attr("roundingMethod", inputNumber.getRoundMethod(), "S")
.attr("caretPositionOnFocus", inputNumber.getCaretPositionOnFocus(), null)
.attr("selectOnFocus", inputNumber.isSelectOnFocus(), true)
.attr("showWarnings", false, true);
wb.finish();
}
@Override
protected String getHighlighter() {
return "inputnumber";
}
/**
* Get the effective minimum Value (as interpreted in the AutoNumeric plugin)
* @param inputNumber the InputNumber component
* @return the minimumValue property as BigDecimal, or the AutoNumeric default value if empty
*/
private BigDecimal getEffectiveMinValue(InputNumber inputNumber) {
String minimumValue = inputNumber.getMinValue();
if (minimumValue == null) {
return DEFAULT_MIN_VALUE;
}
try {
return new BigDecimal(minimumValue);
}
catch (Exception e) {
throw new IllegalArgumentException("Error converting [" + minimumValue + "] to a decimal value for minValue", e);
}
}
/**
* Get the effective maximum Value (as interpreted in the AutoNumeric plugin)
* @param inputNumber the InputNumber component
* @return the maximumValue property as BigDecimal, or the AutoNumeric default value if empty
*/
private BigDecimal getEffectiveMaxValue(InputNumber inputNumber) {
String maximumValue = inputNumber.getMaxValue();
if (maximumValue == null) {
return DEFAULT_MAX_VALUE;
}
try {
return new BigDecimal(maximumValue);
}
catch (Exception e) {
throw new IllegalArgumentException("Error converting [" + maximumValue + "] to a decimal value for maxValue", e);
}
}
/**
* Coerce the provided value to the range defined by the effective minimum and maximum numbers.
* @param value the value to render
* @param inputNumber the component for which the minValue and maxValue properties define the range
* @return the value if inside the range, or else the nearest boundary that is still inside the range
*/
private BigDecimal coerceValueInRange(BigDecimal value, InputNumber inputNumber) {
return coerceValueInRange(value, getEffectiveMinValue(inputNumber), getEffectiveMaxValue(inputNumber));
}
/**
* Coerce the provided value to the range defined by the effective minimum and maximum numbers.
* @param value the value to render
* @param effectiveMinValue the effective minimum value
* @param effectiveMaxValue the effective maximum number
* @return the value if inside the range, or else the nearest boundary that is still inside the range
*/
private BigDecimal coerceValueInRange(BigDecimal value, BigDecimal effectiveMinValue, BigDecimal effectiveMaxValue) {
if (value.compareTo(effectiveMinValue) < 0) {
return effectiveMinValue;
}
if (value.compareTo(effectiveMaxValue) > 0) {
return effectiveMaxValue;
}
return value;
}
private String formatForPlugin(String valueToRender) {
if (valueToRender == null) {
return null;
}
if (isValueBlank(valueToRender)) {
return "";
}
else {
try {
BigDecimal objectToRender = new BigDecimal(valueToRender);
return formatForPlugin(objectToRender);
}
catch (Exception e) {
throw new IllegalArgumentException("Error converting [" + valueToRender + "] to a decimal value;", e);
}
}
}
/**
* Format a BigDecimal value as value/minValue/maxValue for the AutoNumeric plugin.
* @param valueToRender the value to render
* @return BigDecimal value as plain decimal String, without any exponential notation
*/
private String formatForPlugin(BigDecimal valueToRender) {
return valueToRender.toPlainString();
}
/**
* Determines the number of decimal places to use.
* First sees if a value is set and return that.
* Else, if this is an integer type number then default to 0
* decimal places if none was declared else default to 2.
* @param isIntegral flag whether the (expected) value is integral
* @param inputNumber the component
* @return the number of decimal places to use
*/
private String getDecimalPlaces(boolean isIntegral, InputNumber inputNumber) {
if (inputNumber.getDecimalPlaces() != null) {
return inputNumber.getDecimalPlaces();
}
if (isIntegral) {
return "0";
}
return "2";
}
/**
* If using @Positive annotation and this is an Integer default to 0 instead of 0.0001.
* @param isIntegral flag whether the (expected) value is integral
* @param inputNumber the component
* @return the minimum value of the component
*/
private String getMinimum(boolean isIntegral, InputNumber inputNumber) {
String minimum = inputNumber.getMinValue();
if (isIntegral && PositiveClientValidationConstraint.MIN_VALUE.equals(minimum)) {
minimum = "0";
}
return formatForPlugin(minimum);
}
/**
* If using @Negative annotation and this is an Integer default to 0 instead of -0.0001.
* @param isIntegral flag whether the (expected) value is integral
* @param inputNumber the component
* @return the maximum value of the component
*/
private String getMaximum(boolean isIntegral, InputNumber inputNumber) {
String maximum = inputNumber.getMaxValue();
if (isIntegral && NegativeClientValidationConstraint.MAX_VALUE.equals(maximum)) {
maximum = "0";
}
return formatForPlugin(maximum);
}
private boolean isIntegral(FacesContext context, InputNumber inputNumber, Object value) {
if (value != null) {
return value instanceof Long
|| value instanceof Integer
|| value instanceof Short
|| value instanceof BigInteger
|| value instanceof Byte;
}
Class> type = getTypeFromValueExpression(context, inputNumber);
if (type == null) {
return false;
}
return type.isAssignableFrom(Long.class)
|| type.isAssignableFrom(Integer.class)
|| type.isAssignableFrom(Short.class)
|| type.isAssignableFrom(BigInteger.class)
|| type.isAssignableFrom(Byte.class);
}
public Class> getTypeFromValueExpression(FacesContext context, InputNumber inputNumber) {
ValueExpression valueExpression = inputNumber.getValueExpression("value");
if (valueExpression == null) {
return null;
}
try {
return valueExpression.getType(context.getELContext());
}
catch (PropertyNotFoundException e) {
return null;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy