| /* |
| * 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.wicket.markup.html.form; |
| |
| import java.util.Locale; |
| |
| import org.apache.wicket.markup.ComponentTag; |
| import org.apache.wicket.model.IModel; |
| import org.apache.wicket.model.Model; |
| import org.apache.wicket.util.convert.ConversionException; |
| import org.apache.wicket.util.convert.IConverter; |
| import org.apache.wicket.util.lang.Objects; |
| import org.apache.wicket.util.value.IValueMap; |
| import org.apache.wicket.validation.validator.RangeValidator; |
| |
| /** |
| * A {@link TextField} for HTML5 <input> with type <em>number</em>. |
| * <p> |
| * The {@code <input>}'s value will be rendered in floating-point representation, as required by |
| * the <a href="https://www.w3.org/TR/html-markup/input.number.html">HTML specification</a>. Use a simple |
| * {@code TextField} to use a locale specific conversion of numbers. |
| * <p> |
| * Automatically validates the input against the configured {@link #setMinimum(N) min} and |
| * {@link #setMaximum(N) max} attributes. If any of them is <code>null</code> then respective |
| * MIN_VALUE or MAX_VALUE for the number type is used. If the number type has no minimum and/or |
| * maximum value then {@link Double#MIN_VALUE} and {@link Double#MAX_VALUE} are used respectfully. |
| * |
| * @param <N> |
| * the type of the number |
| */ |
| public class NumberTextField<N extends Number & Comparable<N>> extends TextField<N> |
| { |
| private static final long serialVersionUID = 1L; |
| |
| /** |
| * Use this as a marker of step attribute value "any" |
| * Because the w3c spec requires step to be a non-negative digit |
| * greater than zero we use zero as delegate for "any" keyword. |
| */ |
| public static final Double ANY = Double.valueOf(0d); |
| |
| private RangeValidator<N> validator; |
| |
| private IModel<N> minimum; |
| |
| private IModel<N> maximum; |
| |
| private IModel<N> step; |
| |
| /** |
| * Construct. |
| * |
| * @param id |
| * The component id |
| */ |
| public NumberTextField(String id) |
| { |
| this(id, null, null); |
| } |
| |
| |
| /** |
| * Construct. |
| * |
| * @param id |
| * The component id |
| * @param type |
| * The type to use when updating the model for this text field |
| */ |
| public NumberTextField(String id, Class<N> type) |
| { |
| this(id, null, type); |
| } |
| |
| |
| /** |
| * Construct. |
| * |
| * @param id |
| * The component id |
| * @param model |
| * The input value |
| */ |
| public NumberTextField(String id, IModel<N> model) |
| { |
| this(id, model, null); |
| } |
| |
| /** |
| * Construct. |
| * |
| * @param id |
| * The component id |
| * @param model |
| * The input value |
| * @param type |
| * The type to use when updating the model for this text field |
| */ |
| public NumberTextField(String id, IModel<N> model, Class<N> type) |
| { |
| super(id, model, type); |
| |
| validator = null; |
| minimum = Model.of((N)null); |
| maximum = Model.of((N)null); |
| step = Model.of((N)null); |
| } |
| |
| /** |
| * Sets the minimum allowed value |
| * |
| * @param minimum |
| * the minimum allowed value |
| * @return this instance |
| */ |
| public NumberTextField<N> setMinimum(final N minimum) |
| { |
| this.minimum = Model.of(minimum); |
| return this; |
| } |
| |
| /** |
| * Sets the maximum allowed value |
| * |
| * @param maximum |
| * the maximum allowed value |
| * @return this instance |
| */ |
| public NumberTextField<N> setMaximum(final N maximum) |
| { |
| this.maximum = Model.of(maximum); |
| return this; |
| } |
| |
| /** |
| * Sets the step attribute |
| * |
| * @param step |
| * the step attribute |
| * @return this instance |
| */ |
| public NumberTextField<N> setStep(final N step) |
| { |
| this.step = Model.of(step); |
| return this; |
| } |
| |
| /** |
| * Sets the minimum allowed value |
| * |
| * @param minimum |
| * the minimum allowed value |
| * @return this instance |
| */ |
| public NumberTextField<N> setMinimum(final IModel<N> minimum) |
| { |
| this.minimum = minimum; |
| return this; |
| } |
| |
| /** |
| * Sets the maximum allowed value |
| * |
| * @param maximum |
| * the maximum allowed value |
| * @return this instance |
| */ |
| public NumberTextField<N> setMaximum(final IModel<N> maximum) |
| { |
| this.maximum = maximum; |
| return this; |
| } |
| |
| /** |
| * Sets the step attribute |
| * |
| * @param step |
| * the step attribute |
| * @return this instance |
| */ |
| public NumberTextField<N> setStep(final IModel<N> step) |
| { |
| this.step = step; |
| return this; |
| } |
| |
| @Override |
| protected void onConfigure() |
| { |
| super.onConfigure(); |
| |
| if (validator != null) |
| { |
| remove(validator); |
| validator = null; |
| } |
| |
| final N min = minimum.getObject(); |
| final N max = maximum.getObject(); |
| if (min != null || max != null) |
| { |
| validator = RangeValidator.range(min, max); |
| add(validator); |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| private Class<N> getNumberType() |
| { |
| Class<N> numberType = getType(); |
| if (numberType == null && getModelObject() != null) |
| { |
| numberType = (Class<N>)getModelObject().getClass(); |
| } |
| return numberType; |
| } |
| |
| @Override |
| protected void onComponentTag(final ComponentTag tag) |
| { |
| super.onComponentTag(tag); |
| |
| IValueMap attributes = tag.getAttributes(); |
| |
| final N min = minimum.getObject(); |
| if (min != null) |
| { |
| attributes.put("min", Objects.stringValue(min)); |
| } |
| else |
| { |
| attributes.remove("min"); |
| } |
| |
| final N max = maximum.getObject(); |
| if (max != null) |
| { |
| attributes.put("max", Objects.stringValue(max)); |
| } |
| else |
| { |
| attributes.remove("max"); |
| } |
| |
| final N _step = step.getObject(); |
| if (_step != null) |
| { |
| if (_step.doubleValue() == ANY) |
| { |
| attributes.put("step", "any"); |
| } |
| else |
| { |
| attributes.put("step", Objects.stringValue(_step)); |
| } |
| } |
| else |
| { |
| attributes.remove("step"); |
| } |
| } |
| |
| @Override |
| protected String[] getInputTypes() |
| { |
| return new String[] {"number"}; |
| } |
| |
| /** |
| * The formatting for {@link Locale#ENGLISH} might not be compatible with HTML (e.g. group |
| * digits), thus use {@link Objects#stringValue(Object)} instead. |
| * |
| * @return value |
| */ |
| @Override |
| protected String getModelValue() |
| { |
| N value = getModelObject(); |
| if (value == null) |
| { |
| return ""; |
| } |
| else |
| { |
| return Objects.stringValue(value); |
| } |
| } |
| |
| /** |
| * Always use {@link Locale#ENGLISH} to parse the input. |
| */ |
| @Override |
| public void convertInput() |
| { |
| IConverter<N> converter = getConverter(getNumberType()); |
| |
| try |
| { |
| setConvertedInput(converter.convertToObject(getInput(), Locale.ENGLISH)); |
| } |
| catch (ConversionException e) |
| { |
| error(newValidationError(e)); |
| } |
| } |
| } |