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.clockpicker;
23  
24  import java.io.IOException;
25  import java.text.DateFormatSymbols;
26  import java.time.LocalTime;
27  import java.time.format.DateTimeFormatter;
28  import java.util.Locale;
29  import java.util.Objects;
30  
31  import javax.el.ValueExpression;
32  import javax.faces.component.UIComponent;
33  import javax.faces.context.FacesContext;
34  import javax.faces.context.ResponseWriter;
35  import javax.faces.convert.Converter;
36  import javax.faces.convert.ConverterException;
37  
38  import org.primefaces.expression.SearchExpressionUtils;
39  import org.primefaces.extensions.util.Attrs;
40  import org.primefaces.renderkit.InputRenderer;
41  import org.primefaces.util.HTML;
42  import org.primefaces.util.LangUtils;
43  import org.primefaces.util.WidgetBuilder;
44  
45  public class ClockPickerRenderer extends InputRenderer {
46  
47      private static final DateTimeFormatter FORMATTER_24_HOUR = DateTimeFormatter.ofPattern("HH:mm");
48  
49      @Override
50      public void decode(FacesContext context, UIComponent component) {
51  
52          final ClockPicker clockPicker = (ClockPicker) component;
53  
54          if (clockPicker.isDisabled() || clockPicker.isReadonly()) {
55              return;
56          }
57          final String param = clockPicker.getClientId(context) + "_input";
58          final String submittedValue = context.getExternalContext().getRequestParameterMap().get(param);
59  
60          if (null != submittedValue) {
61              clockPicker.setSubmittedValue(submittedValue);
62          }
63          decodeBehaviors(context, component);
64      }
65  
66      @Override
67      public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
68          final ClockPicker clockPicker = (ClockPicker) component;
69          final Locale locale = clockPicker.calculateLocale(context);
70          final DateFormatSymbols symbols = new DateFormatSymbols(locale);
71  
72          final String value = getValueAsString(context, clockPicker);
73          encodeMarkup(context, clockPicker, value, symbols);
74          encodeScript(context, clockPicker, symbols);
75      }
76  
77      private void encodeMarkup(FacesContext context, ClockPicker clockPicker, final String value, DateFormatSymbols symbols) throws IOException {
78          final ResponseWriter writer = context.getResponseWriter();
79          final String clientId = clockPicker.getClientId();
80          final String inputId = clientId + "_input";
81          final String[] ampm = symbols.getAmPmStrings();
82          final int inputSize = clockPicker.isTwelveHour() ? 5 + ampm[0].length() : 5;
83  
84          writer.startElement("div", clockPicker);
85          writer.writeAttribute("id", clientId, null);
86          String containerClass = ClockPicker.CONTAINER_CLASS;
87          if (clockPicker.isShowOnButton()) {
88              containerClass += " ui-inputgroup";
89          }
90          writer.writeAttribute("class", containerClass, null);
91  
92          writer.startElement("input", null);
93          writer.writeAttribute("id", inputId, null);
94          writer.writeAttribute("name", inputId, null);
95          writer.writeAttribute("type", "text", null);
96          writer.writeAttribute("size", inputSize, null);
97          writer.writeAttribute("maxlength", inputSize, null);
98  
99          final String styleClass = createStyleClass(clockPicker, "ui-inputfield ui-widget ui-state-default ui-corner-all");
100         writer.writeAttribute(Attrs.CLASS, styleClass, null);
101 
102         if (clockPicker.getStyle() != null) {
103             writer.writeAttribute(Attrs.STYLE, clockPicker.getStyle(), null);
104         }
105 
106         if (LangUtils.isNotBlank(value)) {
107             writer.writeAttribute("value", value, null);
108         }
109 
110         renderAccessibilityAttributes(context, clockPicker);
111         renderPassThruAttributes(context, clockPicker, HTML.INPUT_TEXT_ATTRS_WITHOUT_EVENTS);
112         renderDomEvents(context, clockPicker, HTML.INPUT_TEXT_EVENTS);
113         renderValidationMetadata(context, clockPicker);
114 
115         writer.endElement("input");
116 
117         if (clockPicker.isShowOnButton()) {
118             writer.startElement("button", null);
119             writer.writeAttribute(Attrs.CLASS, ClockPicker.BUTTON_TRIGGER_CLASS, null);
120             writer.writeAttribute("type", "button", null);
121             writer.writeAttribute("role", "button", null);
122 
123             writer.startElement("span", null);
124             writer.writeAttribute(Attrs.CLASS, ClockPicker.BUTTON_TRIGGER_ICON_CLASS, null);
125             writer.endElement("span");
126 
127             writer.startElement("span", null);
128             writer.writeAttribute(Attrs.CLASS, ClockPicker.BUTTON_TRIGGER_TEXT_CLASS, null);
129             writer.write("ui-button");
130             writer.endElement("span");
131             writer.endElement("button");
132         }
133 
134         writer.endElement("div");
135     }
136 
137     private void encodeScript(final FacesContext context, final ClockPicker clockPicker, DateFormatSymbols symbols) throws IOException {
138         final String[] ampm = symbols.getAmPmStrings();
139 
140         final WidgetBuilder wb = getWidgetBuilder(context);
141         wb.init("ExtClockPicker", clockPicker);
142         wb.attr("appendTo", SearchExpressionUtils.resolveOptionalClientIdForClientSide(context, clockPicker, clockPicker.getAppendTo()));
143         wb.attr("placement", clockPicker.getPlacement(), "bottom");
144         wb.attr("align", clockPicker.getAlign(), "left");
145         wb.attr("autoclose", clockPicker.isAutoClose(), false);
146         wb.attr("vibrate", clockPicker.getVibrate(), true);
147         wb.attr("twelvehour", clockPicker.isTwelveHour(), false);
148         wb.attr("amtext", ampm[0], "AM");
149         wb.attr("pmtext", ampm[1], "PM");
150 
151         if (clockPicker.getOnbeforehourselect() != null) {
152             wb.callback("beforeHourSelect", "function()", clockPicker.getOnbeforehourselect());
153         }
154         if (clockPicker.getOnbeforedone() != null) {
155             wb.callback("beforeDone", "function()", clockPicker.getOnbeforedone());
156         }
157         if (clockPicker.getOnbeforeshow() != null) {
158             wb.callback("beforeShow", "function()", clockPicker.getOnbeforeshow());
159         }
160         if (clockPicker.getOnbeforehide() != null) {
161             wb.callback("beforeHide", "function()", clockPicker.getOnbeforehide());
162         }
163         if (clockPicker.getOnafterhourselect() != null) {
164             wb.callback("afterHourSelect", "function()", clockPicker.getOnafterhourselect());
165         }
166         if (clockPicker.getOnafterdone() != null) {
167             wb.callback("afterDone", "function()", clockPicker.getOnafterdone());
168         }
169         if (clockPicker.getOnaftershow() != null) {
170             wb.callback("afterShow", "function()", clockPicker.getOnaftershow());
171         }
172         if (clockPicker.getOnafterhide() != null) {
173             wb.callback("afterHide", "function()", clockPicker.getOnafterhide());
174         }
175         if (clockPicker.getOnafterampmselect() != null) {
176             wb.callback("afterAmPmSelect", "function()", clockPicker.getOnafterampmselect());
177         }
178 
179         encodeClientBehaviors(context, clockPicker);
180         wb.finish();
181     }
182 
183     protected static String getValueAsString(final FacesContext context, final ClockPicker clockPicker) {
184         Object submittedValue = clockPicker.getSubmittedValue();
185         if (submittedValue != null) {
186             return submittedValue.toString();
187         }
188 
189         Object value = clockPicker.getValue();
190         if (value == null) {
191             return null;
192         }
193 
194         try {
195             if (clockPicker.getConverter() != null) {
196                 return clockPicker.getConverter().getAsString(context, clockPicker, value);
197             }
198             else if (value instanceof LocalTime) {
199                 return clockPicker.isTwelveHour()
200                             ? ((LocalTime) value).format(DateTimeFormatter.ofPattern("hh:mma").withLocale(clockPicker.calculateLocale(context)))
201                             : ((LocalTime) value).format(FORMATTER_24_HOUR);
202             }
203         }
204         catch (Exception e) {
205             return null;
206         }
207 
208         return null;
209     }
210 
211     @Override
212     public Object getConvertedValue(FacesContext context, UIComponent component,
213                 Object value) throws ConverterException {
214         final ClockPicker clockPicker = (ClockPicker) component;
215         String submittedValue = Objects.toString(value, null);
216 
217         if (isValueBlank(submittedValue)) {
218             return null;
219         }
220 
221         // Delegate to user supplied converter if defined
222         try {
223             Converter converter = clockPicker.getConverter();
224             if (converter != null) {
225                 return converter.getAsObject(context, clockPicker, submittedValue);
226             }
227         }
228         catch (ConverterException e) {
229             return submittedValue;
230         }
231 
232         // Delegate to global defined converter (e.g. joda or java8)
233         try {
234             ValueExpression ve = clockPicker.getValueExpression("value");
235             if (ve != null) {
236                 Class<?> type = ve.getType(context.getELContext());
237                 if (type != null && submittedValue != null && type.isAssignableFrom(LocalTime.class)) {
238                     // Use built-in converter for LocalTime
239                     return clockPicker.isTwelveHour()
240                                 ? LocalTime.parse(submittedValue, DateTimeFormatter.ofPattern("hh:mma").withLocale(clockPicker.calculateLocale(context)))
241                                 : LocalTime.parse(submittedValue, FORMATTER_24_HOUR);
242                 }
243             }
244         }
245         catch (Exception e) {
246             return submittedValue;
247         }
248         return submittedValue;
249     }
250 }