1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.primefaces.extensions.component.inputphone;
23
24 import java.io.IOException;
25 import java.util.Arrays;
26 import java.util.Collection;
27 import java.util.Map;
28 import java.util.logging.Logger;
29
30 import javax.faces.FacesException;
31 import javax.faces.component.UIComponent;
32 import javax.faces.context.FacesContext;
33 import javax.faces.context.ResponseWriter;
34 import javax.faces.convert.Converter;
35
36 import org.primefaces.component.inputtext.InputText;
37 import org.primefaces.extensions.config.PrimeExtensionsEnvironment;
38 import org.primefaces.extensions.util.Attrs;
39 import org.primefaces.extensions.util.PhoneNumberUtilWrapper;
40 import org.primefaces.renderkit.InputRenderer;
41 import org.primefaces.shaded.json.JSONArray;
42 import org.primefaces.shaded.json.JSONObject;
43 import org.primefaces.util.ComponentUtils;
44 import org.primefaces.util.Constants;
45 import org.primefaces.util.HTML;
46 import org.primefaces.util.LangUtils;
47 import org.primefaces.util.WidgetBuilder;
48
49
50
51
52
53
54
55 public class InputPhoneRenderer extends InputRenderer {
56
57 private static final Logger LOGGER = Logger.getLogger(InputPhoneRenderer.class.getName());
58 private static final String HIDDEN_ID = "_hidden";
59 private static final String ISO2_ID = "_iso2";
60
61 @Override
62 public void decode(final FacesContext context, final UIComponent component) {
63 final InputPhone inputPhone = (InputPhone) component;
64
65 if (!shouldDecode(inputPhone)) {
66 return;
67 }
68
69 decodeBehaviors(context, inputPhone);
70
71 final String inputId = inputPhone.getClientId(context) + HIDDEN_ID;
72 final String submittedValue = context.getExternalContext().getRequestParameterMap().get(inputId);
73
74 if (submittedValue != null) {
75 inputPhone.setSubmittedValue(submittedValue);
76 }
77 }
78
79 @Override
80 public void encodeEnd(final FacesContext context, final UIComponent component) throws IOException {
81 final InputPhone inputPhone = (InputPhone) component;
82
83 final Object value = inputPhone.getValue();
84 String valueToRender = ComponentUtils.getValueToRender(context, inputPhone, value);
85 if (valueToRender == null) {
86 valueToRender = Constants.EMPTY_STRING;
87 }
88
89 encodeMarkup(context, inputPhone, valueToRender);
90 encodeScript(context, inputPhone);
91 }
92
93 @Override
94 public Object getConvertedValue(final FacesContext context, final UIComponent component,
95 final Object submittedValue) {
96 final String value = (String) submittedValue;
97 if (LangUtils.isBlank(value)) {
98 return null;
99 }
100
101 final InputPhone inputPhone = (InputPhone) component;
102 final Converter<?> converter = inputPhone.getConverter();
103
104 if (converter != null) {
105 return converter.getAsObject(context, inputPhone, value);
106 }
107
108 String country = context.getExternalContext().getRequestParameterMap()
109 .get(inputPhone.getClientId() + ISO2_ID);
110 if (country == null || InputPhone.COUNTRY_AUTO.equals(country)) {
111 country = Constants.EMPTY_STRING;
112 }
113 else {
114 inputPhone.setInitialCountry(country);
115 }
116 if (PrimeExtensionsEnvironment.getCurrentInstance(context).isLibphonenumberAvailable()) {
117 PhoneNumberUtilWrapper.validate(value, country.toUpperCase(), inputPhone.getValidatorMessage());
118 }
119 else {
120 LOGGER.warning("Libphonenumber not available, unable to validate!");
121 }
122 return value;
123 }
124
125 protected void encodeMarkup(final FacesContext context, final InputPhone inputPhone, final String valueToRender)
126 throws IOException {
127 final ResponseWriter writer = context.getResponseWriter();
128 final String clientId = inputPhone.getClientId(context);
129 final String styleClass = getStyleClassBuilder(context)
130 .add(InputPhone.STYLE_CLASS)
131 .add(inputPhone.getStyleClass())
132 .build();
133
134 writer.startElement("span", inputPhone);
135 writer.writeAttribute("id", clientId, null);
136 writer.writeAttribute(Attrs.CLASS, styleClass, "styleClass");
137
138 if (inputPhone.getStyle() != null) {
139 writer.writeAttribute(Attrs.STYLE, inputPhone.getStyle(), Attrs.STYLE);
140 }
141
142 renderRTLDirection(context, inputPhone);
143 encodeInput(context, inputPhone, clientId, valueToRender);
144 encodeHiddenInputs(context, inputPhone, clientId, valueToRender);
145
146 writer.endElement("span");
147 }
148
149 protected void encodeInput(final FacesContext context, final InputPhone inputPhone, final String clientId,
150 final String valueToRender)
151 throws IOException {
152
153 final ResponseWriter writer = context.getResponseWriter();
154 final String inputId = clientId + InputPhone.INPUT_SUFFIX;
155 final String inputStyle = inputPhone.getInputStyle();
156 final String styleClass = createStyleClass(inputPhone, "inputStyleClass", InputText.STYLE_CLASS);
157
158 writer.startElement("input", null);
159 writer.writeAttribute("id", inputId, null);
160 writer.writeAttribute("name", inputId, null);
161 writer.writeAttribute("type", inputPhone.getType(), null);
162 writer.writeAttribute("value", valueToRender, null);
163 writer.writeAttribute(Attrs.CLASS, styleClass, "inputStyleClass");
164
165 if (!isValueBlank(inputStyle)) {
166 writer.writeAttribute(Attrs.STYLE, inputStyle, null);
167 }
168
169 renderAccessibilityAttributes(context, inputPhone);
170 renderPassThruAttributes(context, inputPhone, HTML.INPUT_TEXT_ATTRS_WITHOUT_EVENTS);
171 renderDomEvents(context, inputPhone, HTML.INPUT_TEXT_EVENTS);
172 renderValidationMetadata(context, inputPhone);
173
174 writer.endElement("input");
175 }
176
177 protected void encodeHiddenInputs(final FacesContext context, final InputPhone inputPhone, final String clientId, final String valueToRender)
178 throws IOException {
179 renderHiddenInput(context, clientId + ISO2_ID, inputPhone.getInitialCountry(), inputPhone.isDisabled());
180 renderHiddenInput(context, clientId + HIDDEN_ID, valueToRender, inputPhone.isDisabled());
181 }
182
183 protected void encodeScript(final FacesContext context, final InputPhone inputPhone) throws IOException {
184 final WidgetBuilder wb = getWidgetBuilder(context);
185 wb.init("ExtInputPhone", inputPhone);
186 encodeCountries(wb, "excludeCountries", inputPhone.getExcludeCountries());
187 wb.attr("allowDropdown", inputPhone.isAllowDropdown(), true);
188 wb.attr("autoHideDialCode", inputPhone.isAutoHideDialCode(), true);
189 wb.attr("fixDropdownWidth", inputPhone.isFixDropdownWidth(), true);
190 wb.attr("formatOnDisplay", inputPhone.isFormatOnDisplay(), true);
191 wb.attr("formatAsYouType", inputPhone.isFormatAsYouType(), true);
192 wb.attr("nationalMode", inputPhone.isNationalMode(), true);
193 wb.attr("separateDialCode", inputPhone.isSeparateDialCode(), false);
194 if (inputPhone.getAutoPlaceholderEnum() != InputPhone.AutoPlaceholder.polite) {
195 wb.attr("autoPlaceholder", inputPhone.getAutoPlaceholder());
196 }
197 if (LangUtils.isNotBlank(inputPhone.getInitialCountry())) {
198 wb.attr("initialCountry", inputPhone.getInitialCountry());
199 }
200 if (InputPhone.COUNTRY_AUTO.equals(inputPhone.getInitialCountry())) {
201 if (inputPhone.getGeoIpLookup() == null) {
202 throw new FacesException("InputPhone geoIpLookup property is required when initialCountry is 'auto'.");
203 }
204 wb.nativeAttr("geoIpLookup", inputPhone.getGeoIpLookup());
205 }
206 encodeCountries(wb, "onlyCountries", inputPhone.getOnlyCountries());
207 if (inputPhone.getPlaceholderNumberTypeEnum() != InputPhone.PlaceholderNumberType.mobile) {
208 wb.attr("placeholderNumberType", inputPhone.getPlaceholderNumberType().toUpperCase());
209 }
210 encodeCountries(wb, "countryOrder", inputPhone.getPreferredCountries());
211
212 wb.attr("utilsScript",
213 context.getApplication()
214 .getResourceHandler()
215 .createResource("inputphone/utils.js", org.primefaces.extensions.util.Constants.LIBRARY)
216 .getRequestPath());
217 if (inputPhone.getLocalizedCountries() != null) {
218 wb.nativeAttr("i18n", objectToJsonString(inputPhone.getLocalizedCountries()));
219 }
220
221 encodeClientBehaviors(context, inputPhone);
222
223 wb.finish();
224 }
225
226 private void encodeCountries(final WidgetBuilder wb, final String attribute, final Object value)
227 throws IOException {
228 final Collection<String> countries = toCollection(value);
229 if (!countries.isEmpty()) {
230 wb.nativeAttr(attribute, new JSONArray(countries).toString());
231 }
232 }
233
234 @SuppressWarnings("unchecked")
235 private Collection<String> toCollection(final Object object) {
236 if (object instanceof String) {
237 final String string = ((String) object).replace(' ', ',').toLowerCase();
238 return Arrays.asList(string.split(","));
239 }
240 return (Collection<String>) object;
241 }
242
243 @SuppressWarnings("unchecked")
244 private String objectToJsonString(final Object object) {
245 if (object == null || object instanceof String) {
246 return (String) object;
247 }
248 Map<String, String> map = (Map<String, String>) object;
249 JSONObject jsonObj = new JSONObject();
250 map.forEach(jsonObj::put);
251 return jsonObj.toString();
252 }
253
254 }