View Javadoc
1   /*
2    * Copyright (c) 2011-2024 PrimeFaces Extensions
3    *
4    *  Permission is hereby granted, free of charge, to any person obtaining a copy
5    *  of this software and associated documentation files (the "Software"), to deal
6    *  in the Software without restriction, including without limitation the rights
7    *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8    *  copies of the Software, and to permit persons to whom the Software is
9    *  furnished to do so, subject to the following conditions:
10   *
11   *  The above copyright notice and this permission notice shall be included in
12   *  all copies or substantial portions of the Software.
13   *
14   *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15   *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16   *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17   *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18   *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19   *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20   *  THE SOFTWARE.
21   */
22  package org.primefaces.extensions.component.inputotp;
23  
24  import java.io.IOException;
25  
26  import javax.faces.FacesException;
27  import javax.faces.component.UIComponent;
28  import javax.faces.context.FacesContext;
29  import javax.faces.context.ResponseWriter;
30  
31  import org.primefaces.extensions.util.Attrs;
32  import org.primefaces.renderkit.InputRenderer;
33  import org.primefaces.util.ComponentUtils;
34  import org.primefaces.util.Constants;
35  import org.primefaces.util.FacetUtils;
36  import org.primefaces.util.HTML;
37  import org.primefaces.util.LangUtils;
38  import org.primefaces.util.WidgetBuilder;
39  
40  /**
41   * Renderer for the {@link InputOtp} component.
42   *
43   * @since 14.0.0
44   */
45  public class InputOtpRenderer extends InputRenderer {
46  
47      @Override
48      public void decode(final FacesContext context, final UIComponent component) {
49          final InputOtp inputOtp = (InputOtp) component;
50  
51          if (!shouldDecode(inputOtp)) {
52              return;
53          }
54  
55          final String inputId = inputOtp.getClientId(context) + InputOtp.HIDDEN_SUFFIX;
56          final String submittedValue = context.getExternalContext().getRequestParameterMap().get(inputId);
57  
58          if (submittedValue != null) {
59              inputOtp.setSubmittedValue(submittedValue);
60          }
61  
62          decodeBehaviors(context, inputOtp);
63      }
64  
65      @Override
66      public void encodeEnd(final FacesContext context, final UIComponent component) throws IOException {
67          final InputOtp inputOtp = (InputOtp) component;
68  
69          if (inputOtp.getLength() < 1) {
70              throw new FacesException("InputOtp length property should be > 0");
71          }
72  
73          String valueToRender = ComponentUtils.getValueToRender(context, inputOtp, inputOtp.getValue());
74          if (valueToRender == null) {
75              valueToRender = Constants.EMPTY_STRING;
76          }
77  
78          encodeMarkup(context, inputOtp, valueToRender);
79          encodeScript(context, inputOtp);
80      }
81  
82      protected void encodeMarkup(final FacesContext context, final InputOtp inputOtp, final String valueToRender)
83                  throws IOException {
84          final ResponseWriter writer = context.getResponseWriter();
85          final String clientId = inputOtp.getClientId(context);
86          final String styleClass = getStyleClassBuilder(context)
87                      .add(InputOtp.STYLE_CLASS, inputOtp.getStyleClass())
88                      .add(ComponentUtils.isRTL(context, inputOtp), InputOtp.RTL_STYLE_CLASS)
89                      .build();
90  
91          writer.startElement("span", inputOtp);
92          writer.writeAttribute("id", clientId, null);
93          writer.writeAttribute(Attrs.CLASS, styleClass, "styleClass");
94  
95          if (inputOtp.getStyle() != null) {
96              writer.writeAttribute(Attrs.STYLE, inputOtp.getStyle(), Attrs.STYLE);
97          }
98  
99          encodeInput(context, inputOtp, clientId, valueToRender);
100         encodeHiddenInput(context, inputOtp, clientId, valueToRender);
101 
102         writer.endElement("span");
103     }
104 
105     protected void encodeInput(final FacesContext context, final InputOtp inputOtp, final String clientId,
106                 final String valueToRender)
107                 throws IOException {
108 
109         final ResponseWriter writer = context.getResponseWriter();
110         final String inputStyle = inputOtp.getInputStyle();
111         final String inputStyleClass = createStyleClass(inputOtp, InputOtp.PropertyKeys.inputStyleClass.name(), InputOtp.CELL_STYLE_CLASS);
112         final char[] chars = valueToRender.toCharArray();
113         final boolean hasSeparatorFacet = FacetUtils.shouldRenderFacet(inputOtp.getFacet("separator"));
114         for (int i = 1; i <= inputOtp.getLength(); i++) {
115 
116             if (i > 1 && (LangUtils.isNotBlank(inputOtp.getSeparator()) || hasSeparatorFacet)) {
117                 writer.startElement("div", null);
118                 writer.writeAttribute(Attrs.CLASS, InputOtp.SEPARATOR_STYLE_CLASS, null);
119                 if (hasSeparatorFacet) {
120                     inputOtp.getFacet("separator").encodeAll(context);
121                 }
122                 else {
123                     writer.writeText(inputOtp.getSeparator(), InputOtp.PropertyKeys.separator.name());
124                 }
125                 writer.endElement("div");
126             }
127 
128             final String inputId = clientId + InputOtp.INPUT_SUFFIX + i;
129             final String inputValue = chars.length >= i ? String.valueOf(chars[i - 1]) : "";
130 
131             writer.startElement("input", null);
132             writer.writeAttribute("id", inputId, null);
133             writer.writeAttribute("name", inputId, null);
134             writer.writeAttribute("value", inputValue, null);
135             writer.writeAttribute("size", 1, null);
136             writer.writeAttribute("maxlength", inputOtp.getLength(), null);
137             writer.writeAttribute(Attrs.CLASS, inputStyleClass, null);
138 
139             if (LangUtils.isNotBlank(inputStyle)) {
140                 writer.writeAttribute(Attrs.STYLE, inputStyle, null);
141             }
142 
143             if (inputOtp.isMask()) {
144                 writer.writeAttribute("type", "password", null);
145             }
146 
147             if (inputOtp.isIntegerOnly() && LangUtils.isBlank(inputOtp.getInputmode())) {
148                 inputOtp.setInputmode("numeric");
149             }
150 
151             if (LangUtils.isNotBlank(inputOtp.getPlaceholder())) {
152                 char placeholder = inputOtp.getPlaceholder().charAt((i - 1) % inputOtp.getPlaceholder().length());
153                 writer.writeAttribute("placeholder", placeholder, null);
154             }
155 
156             renderAccessibilityAttributes(context, inputOtp);
157             renderRTLDirection(context, inputOtp);
158             renderPassThruAttributes(context, inputOtp, InputOtp.INPUT_OTP_ATTRIBUTES_WITHOUT_EVENTS);
159             renderDomEvents(context, inputOtp, HTML.INPUT_TEXT_EVENTS);
160             renderValidationMetadata(context, inputOtp);
161 
162             writer.endElement("input");
163         }
164     }
165 
166     protected void encodeHiddenInput(final FacesContext context, final InputOtp inputOtp, final String clientId, final String valueToRender)
167                 throws IOException {
168         renderHiddenInput(context, clientId + InputOtp.HIDDEN_SUFFIX, valueToRender, inputOtp.isDisabled());
169     }
170 
171     protected void encodeScript(final FacesContext context, final InputOtp inputOtp) throws IOException {
172         final WidgetBuilder wb = getWidgetBuilder(context);
173         wb.init("ExtInputOtp", inputOtp);
174         wb.attr("integerOnly", inputOtp.isIntegerOnly(), false);
175         if (LangUtils.isNotBlank(inputOtp.getAriaLabel())) {
176             wb.attr("ariaLabel", inputOtp.getAriaLabel());
177         }
178 
179         encodeClientBehaviors(context, inputOtp);
180 
181         wb.finish();
182     }
183 
184 }