001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.markup.html.form; 018 019import java.util.Locale; 020 021import org.apache.wicket.markup.ComponentTag; 022import org.apache.wicket.model.IModel; 023import org.apache.wicket.model.Model; 024import org.apache.wicket.util.convert.ConversionException; 025import org.apache.wicket.util.convert.IConverter; 026import org.apache.wicket.util.lang.Objects; 027import org.apache.wicket.util.value.IValueMap; 028import org.apache.wicket.validation.validator.RangeValidator; 029 030/** 031 * A {@link TextField} for HTML5 <input> with type <em>number</em>. 032 * <p> 033 * The {@code <input>}'s value will be rendered in floating-point representation, as required by 034 * the <a href="https://www.w3.org/TR/html-markup/input.number.html">HTML specification</a>. Use a simple 035 * {@code TextField} to use a locale specific conversion of numbers. 036 * <p> 037 * Automatically validates the input against the configured {@link #setMinimum(Number) min} and 038 * {@link #setMaximum(Number) max} attributes. If any of them is <code>null</code> then respective 039 * MIN_VALUE or MAX_VALUE for the number type is used. If the number type has no minimum and/or 040 * maximum value then {@link Double#MIN_VALUE} and {@link Double#MAX_VALUE} are used respectfully. 041 * 042 * @param <N> 043 * the type of the number 044 */ 045public class NumberTextField<N extends Number & Comparable<N>> extends TextField<N> 046{ 047 private static final long serialVersionUID = 1L; 048 049 /** 050 * Use this as a marker of step attribute value "any" 051 * Because the w3c spec requires step to be a non-negative digit 052 * greater than zero we use zero as delegate for "any" keyword. 053 */ 054 public static final Double ANY = Double.valueOf(0d); 055 056 private RangeValidator<N> validator; 057 058 private IModel<N> minimum; 059 060 private IModel<N> maximum; 061 062 private IModel<N> step; 063 064 /** 065 * Construct. 066 * 067 * @param id 068 * The component id 069 */ 070 public NumberTextField(String id) 071 { 072 this(id, null, null); 073 } 074 075 076 /** 077 * Construct. 078 * 079 * @param id 080 * The component id 081 * @param type 082 * The type to use when updating the model for this text field 083 */ 084 public NumberTextField(String id, Class<N> type) 085 { 086 this(id, null, type); 087 } 088 089 090 /** 091 * Construct. 092 * 093 * @param id 094 * The component id 095 * @param model 096 * The input value 097 */ 098 public NumberTextField(String id, IModel<N> model) 099 { 100 this(id, model, null); 101 } 102 103 /** 104 * Construct. 105 * 106 * @param id 107 * The component id 108 * @param model 109 * The input value 110 * @param type 111 * The type to use when updating the model for this text field 112 */ 113 public NumberTextField(String id, IModel<N> model, Class<N> type) 114 { 115 super(id, model, type); 116 117 validator = null; 118 minimum = Model.of((N)null); 119 maximum = Model.of((N)null); 120 step = Model.of((N)null); 121 } 122 123 /** 124 * Sets the minimum allowed value 125 * 126 * @param minimum 127 * the minimum allowed value 128 * @return this instance 129 */ 130 public NumberTextField<N> setMinimum(final N minimum) 131 { 132 this.minimum = Model.of(minimum); 133 return this; 134 } 135 136 /** 137 * Sets the maximum allowed value 138 * 139 * @param maximum 140 * the maximum allowed value 141 * @return this instance 142 */ 143 public NumberTextField<N> setMaximum(final N maximum) 144 { 145 this.maximum = Model.of(maximum); 146 return this; 147 } 148 149 /** 150 * Sets the step attribute 151 * 152 * @param step 153 * the step attribute 154 * @return this instance 155 */ 156 public NumberTextField<N> setStep(final N step) 157 { 158 this.step = Model.of(step); 159 return this; 160 } 161 162 /** 163 * Sets the minimum allowed value 164 * 165 * @param minimum 166 * the minimum allowed value 167 * @return this instance 168 */ 169 public NumberTextField<N> setMinimum(final IModel<N> minimum) 170 { 171 this.minimum = minimum; 172 return this; 173 } 174 175 /** 176 * Sets the maximum allowed value 177 * 178 * @param maximum 179 * the maximum allowed value 180 * @return this instance 181 */ 182 public NumberTextField<N> setMaximum(final IModel<N> maximum) 183 { 184 this.maximum = maximum; 185 return this; 186 } 187 188 /** 189 * Sets the step attribute 190 * 191 * @param step 192 * the step attribute 193 * @return this instance 194 */ 195 public NumberTextField<N> setStep(final IModel<N> step) 196 { 197 this.step = step; 198 return this; 199 } 200 201 @Override 202 protected void onConfigure() 203 { 204 super.onConfigure(); 205 206 if (validator != null) 207 { 208 remove(validator); 209 validator = null; 210 } 211 212 final N min = minimum.getObject(); 213 final N max = maximum.getObject(); 214 if (min != null || max != null) 215 { 216 validator = RangeValidator.range(min, max); 217 add(validator); 218 } 219 } 220 221 @SuppressWarnings("unchecked") 222 private Class<N> getNumberType() 223 { 224 Class<N> numberType = getType(); 225 if (numberType == null && getModelObject() != null) 226 { 227 numberType = (Class<N>)getModelObject().getClass(); 228 } 229 return numberType; 230 } 231 232 @Override 233 protected void onComponentTag(final ComponentTag tag) 234 { 235 super.onComponentTag(tag); 236 237 IValueMap attributes = tag.getAttributes(); 238 239 final N min = minimum.getObject(); 240 if (min != null) 241 { 242 attributes.put("min", Objects.stringValue(min)); 243 } 244 else 245 { 246 attributes.remove("min"); 247 } 248 249 final N max = maximum.getObject(); 250 if (max != null) 251 { 252 attributes.put("max", Objects.stringValue(max)); 253 } 254 else 255 { 256 attributes.remove("max"); 257 } 258 259 final N _step = step.getObject(); 260 if (_step != null) 261 { 262 if (_step.doubleValue() == ANY) 263 { 264 attributes.put("step", "any"); 265 } 266 else 267 { 268 attributes.put("step", Objects.stringValue(_step)); 269 } 270 } 271 else 272 { 273 attributes.remove("step"); 274 } 275 } 276 277 @Override 278 protected String[] getInputTypes() 279 { 280 return new String[] {"number"}; 281 } 282 283 /** 284 * The formatting for {@link Locale#ENGLISH} might not be compatible with HTML (e.g. group 285 * digits), thus use {@link Objects#stringValue(Object)} instead. 286 * 287 * @return value 288 */ 289 @Override 290 protected String getModelValue() 291 { 292 N value = getModelObject(); 293 if (value == null) 294 { 295 return ""; 296 } 297 else 298 { 299 return Objects.stringValue(value); 300 } 301 } 302 303 /** 304 * Always use {@link Locale#ENGLISH} to parse the input. 305 */ 306 @Override 307 public void convertInput() 308 { 309 IConverter<N> converter = getConverter(getNumberType()); 310 311 try 312 { 313 setConvertedInput(converter.convertToObject(getInput(), Locale.ENGLISH)); 314 } 315 catch (ConversionException e) 316 { 317 error(newValidationError(e)); 318 } 319 } 320}