View Javadoc
1   /*
2    * Copyright (c) 2002-2026 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.html;
16  
17  import java.util.Map;
18  
19  import org.htmlunit.SgmlPage;
20  import org.htmlunit.util.StringUtils;
21  
22  /**
23   * Wrapper for the HTML element "input" where type is "range".
24   *
25   * @author Ahmed Ashour
26   * @author Ronald Brill
27   * @author Frank Danek
28   * @author Anton Demydenko
29   */
30  public class HtmlRangeInput extends HtmlInput implements LabelableElement {
31  
32      /**
33       * Creates an instance.
34       *
35       * @param qualifiedName the qualified name of the element type to instantiate
36       * @param page the page that contains this element
37       * @param attributes the initial attributes
38       */
39      HtmlRangeInput(final String qualifiedName, final SgmlPage page,
40              final Map<String, DomAttr> attributes) {
41          super(qualifiedName, page, attributes);
42  
43          final String value = getValueAttribute();
44          if (value == ATTRIBUTE_NOT_DEFINED) {
45              final double min = getMinNumeric();
46              final double max = getMaxNumeric();
47              if (max < min) {
48                  setValue(min);
49                  unmarkValueDirty();
50                  return;
51              }
52  
53              final double val = min + ((max - min) / 2);
54              setValue(val);
55              unmarkValueDirty();
56          }
57          else {
58              setValue(value);
59              unmarkValueDirty();
60          }
61      }
62  
63      /**
64       * Returns the numeric value of the {@code min} attribute.
65       *
66       * @return the value of the {@code min} attribute as a {@code double}, or
67       *         {@code 0} if the attribute is not defined or cannot be parsed
68       */
69      public double getMinNumeric() {
70          final String min = getAttributeDirect("min");
71          if (min == ATTRIBUTE_NOT_DEFINED) {
72              return 0;
73          }
74          try {
75              return Double.parseDouble(min);
76          }
77          catch (final NumberFormatException e) {
78              return 0;
79          }
80      }
81  
82      /**
83       * Returns the numeric value of the {@code max} attribute.
84       *
85       * @return the value of the {@code max} attribute as a {@code double}, or
86       *         {@code 100} if the attribute is not defined or cannot be parsed
87       */
88      public double getMaxNumeric() {
89          final String max = getAttributeDirect("max");
90          if (max == ATTRIBUTE_NOT_DEFINED) {
91              return 100;
92          }
93          try {
94              return Double.parseDouble(max);
95          }
96          catch (final NumberFormatException e) {
97              return 100;
98          }
99      }
100 
101     /**
102      * Returns the numeric value of the {@code step} attribute.
103      *
104      * @return the value of the {@code step} attribute as a {@code double}, or
105      *         {@code 1} if the attribute is not defined or cannot be parsed
106      */
107     public double getStepNumeric() {
108         final String step = getAttributeDirect("step");
109         if (step == ATTRIBUTE_NOT_DEFINED) {
110             return 1;
111         }
112         try {
113             return Double.parseDouble(step);
114         }
115         catch (final NumberFormatException e) {
116             return 1;
117         }
118     }
119 
120     /**
121      * {@inheritDoc}
122      */
123     @Override
124     public void setDefaultChecked(final boolean defaultChecked) {
125         // Empty.
126     }
127 
128     /**
129      * {@inheritDoc}
130      */
131     @Override
132     public void setValue(final String newValue) {
133         try {
134             if (!StringUtils.isEmptyOrNull(newValue)) {
135                 setValue(Double.parseDouble(newValue));
136             }
137             else {
138                 final double min = getMinNumeric();
139                 final double max = getMaxNumeric();
140 
141                 // place in the middle
142                 setValue(min + ((max - min) / 2));
143             }
144         }
145         catch (final NumberFormatException ignored) {
146             // ignore
147         }
148     }
149 
150     private void setValue(final double newValue) {
151         double value = newValue;
152 
153         final double min = getMinNumeric();
154         final double max = getMaxNumeric();
155 
156         if (value > max) {
157             value = max;
158         }
159         else {
160             if (value < min) {
161                 value = min;
162             }
163         }
164 
165         final double step = getStepNumeric();
166         value = value - min;
167         int fact = (int) (value / step);
168         final double rest = value % step;
169         if (rest >= step / 2) {
170             fact++;
171         }
172         value = min + step * fact;
173 
174         if (!Double.isInfinite(value) && (value == Math.floor(value))) {
175             super.setValue(Integer.toString((int) value));
176         }
177         else {
178             super.setValue(Double.toString(value));
179         }
180     }
181 
182     @Override
183     protected void valueAttributeChanged(final String attributeValue, final boolean isValueDirty) {
184         if (isValueDirty) {
185             return;
186         }
187 
188         try {
189             if (!org.htmlunit.util.StringUtils.isEmptyOrNull(attributeValue)) {
190                 setRawValue(Double.parseDouble(attributeValue));
191             }
192             else {
193                 final double min = getMinNumeric();
194                 final double max = getMaxNumeric();
195 
196                 // place in the middle
197                 setRawValue(min + ((max - min) / 2));
198             }
199         }
200         catch (final NumberFormatException ignored) {
201             // ignore
202         }
203     }
204 
205     private void setRawValue(final double newValue) {
206         double value = newValue;
207 
208         final double min = getMinNumeric();
209         final double max = getMaxNumeric();
210 
211         if (value > max) {
212             value = max;
213         }
214         else {
215             if (value < min) {
216                 value = min;
217             }
218         }
219 
220         final double step = getStepNumeric();
221         value = value - min;
222         int fact = (int) (value / step);
223         final double rest = value % step;
224         if (rest >= step / 2) {
225             fact++;
226         }
227         value = min + step * fact;
228 
229         if (!Double.isInfinite(value) && value == Math.floor(value)) {
230             setRawValue(Integer.toString((int) value));
231         }
232         else {
233             setRawValue(Double.toString(value));
234         }
235     }
236 
237     /**
238      * {@inheritDoc}
239      */
240     @Override
241     protected boolean isRequiredSupported() {
242         return false;
243     }
244 
245     /**
246      * {@inheritDoc}
247      */
248     @Override
249     public boolean isValid() {
250         return super.isValid() && isMaxValid() && isMinValid();
251     }
252 
253     /**
254      * Returns if the input element has a valid min value. Refer to the
255      * <a href="https://www.w3.org/TR/html5/sec-forms.html">HTML 5</a>
256      * documentation for details.
257      *
258      * @return if the input element has a valid min value
259      */
260     private boolean isMinValid() {
261         if (!getRawValue().isEmpty() && !getMin().isEmpty()) {
262             try {
263                 final double value = Double.parseDouble(getRawValue());
264                 return getMinNumeric() <= value;
265             }
266             catch (final NumberFormatException ignored) {
267                 // ignore
268             }
269         }
270         return true;
271     }
272 
273     /**
274      * Returns if the input element has a valid max value. Refer to the
275      * <a href="https://www.w3.org/TR/html5/sec-forms.html">HTML 5</a>
276      * documentation for details.
277      *
278      * @return if the input element has a valid max value
279      */
280     private boolean isMaxValid() {
281         if (!getRawValue().isEmpty() && !getMax().isEmpty()) {
282             try {
283                 final int value = Integer.parseInt(getRawValue());
284                 return getMaxNumeric() >= value;
285             }
286             catch (final NumberFormatException ignored) {
287                 // ignore
288             }
289         }
290         return true;
291     }
292 }