View Javadoc
1   /*
2    * Copyright (c) 2002-2025 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 static org.htmlunit.BrowserVersionFeatures.HTMLINPUT_TYPE_MONTH_SUPPORTED;
18  import static org.htmlunit.BrowserVersionFeatures.JS_INPUT_CHANGE_TYPE_DROPS_VALUE_WEEK_MONTH;
19  
20  import java.time.YearMonth;
21  import java.time.format.DateTimeFormatter;
22  import java.time.format.DateTimeParseException;
23  import java.util.Map;
24  
25  import org.apache.commons.lang3.StringUtils;
26  import org.htmlunit.BrowserVersion;
27  import org.htmlunit.SgmlPage;
28  
29  /**
30   * Wrapper for the HTML element "input" where type is "month".
31   *
32   * @author Ahmed Ashour
33   * @author Frank Danek
34   * @author Anton Demydenko
35   */
36  public class HtmlMonthInput extends HtmlInput implements LabelableElement {
37  
38      private static final DateTimeFormatter FORMATTER_ = DateTimeFormatter.ofPattern("yyyy-MM");
39  
40      /**
41       * Creates an instance.
42       *
43       * @param qualifiedName the qualified name of the element type to instantiate
44       * @param page the page that contains this element
45       * @param attributes the initial attributes
46       */
47      HtmlMonthInput(final String qualifiedName, final SgmlPage page,
48              final Map<String, DomAttr> attributes) {
49          super(qualifiedName, page, attributes);
50      }
51  
52      /**
53       * {@inheritDoc}
54       */
55      @Override
56      public void setDefaultChecked(final boolean defaultChecked) {
57          // Empty.
58      }
59  
60      /**
61       * {@inheritDoc}
62       */
63      @Override
64      public void setValue(final String newValue) {
65          try {
66              if (hasFeature(HTMLINPUT_TYPE_MONTH_SUPPORTED) && StringUtils.isNotEmpty(newValue)) {
67                  FORMATTER_.parse(newValue);
68              }
69              super.setValue(newValue);
70          }
71          catch (final DateTimeParseException ignored) {
72              // ignore
73          }
74      }
75  
76      /**
77       * {@inheritDoc}
78       */
79      @Override
80      public boolean isValid() {
81          return super.isValid() && isMaxValid() && isMinValid();
82      }
83  
84      /**
85       * Returns if the input element has a valid min value. Refer to the
86       * <a href="https://www.w3.org/TR/html5/sec-forms.html">HTML 5</a>
87       * documentation for details.
88       *
89       * @return if the input element has a valid min value
90       */
91      private boolean isMinValid() {
92          if (hasFeature(HTMLINPUT_TYPE_MONTH_SUPPORTED) && !getMin().isEmpty()) {
93              try {
94                  final YearMonth dateValue = YearMonth.parse(getRawValue(), FORMATTER_);
95                  final YearMonth minDate = YearMonth.parse(getMin(), FORMATTER_);
96                  return minDate.equals(dateValue) || minDate.isBefore(dateValue);
97              }
98              catch (final DateTimeParseException ignored) {
99                  // ignore
100             }
101         }
102         return true;
103     }
104 
105     /**
106      * Returns if the input element has a valid max value. Refer to the
107      * <a href="https://www.w3.org/TR/html5/sec-forms.html">HTML 5</a>
108      * documentation for details.
109      *
110      * @return if the input element has a valid max value
111      */
112     private boolean isMaxValid() {
113         if (hasFeature(HTMLINPUT_TYPE_MONTH_SUPPORTED) && !getMax().isEmpty()) {
114             try {
115                 final YearMonth dateValue = YearMonth.parse(getRawValue(), FORMATTER_);
116                 final YearMonth maxDate = YearMonth.parse(getMax(), FORMATTER_);
117                 return maxDate.equals(dateValue) || maxDate.isAfter(dateValue);
118             }
119             catch (final DateTimeParseException ignored) {
120                 // ignore
121             }
122         }
123         return true;
124     }
125 
126     @Override
127     protected void adjustValueAfterTypeChange(final HtmlInput oldInput, final BrowserVersion browserVersion) {
128         if (browserVersion.hasFeature(JS_INPUT_CHANGE_TYPE_DROPS_VALUE_WEEK_MONTH)) {
129             setValue("");
130             return;
131         }
132         super.adjustValueAfterTypeChange(oldInput, browserVersion);
133     }
134 }