PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.16
Yatra – Travel Booking & Tour Operator Software v3.0.16
3.0.16 3.0.15 3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 All 84 releases
yatra / assets / js / phone-input.js

phone-input.js in Yatra – Travel Booking & Tour Operator Software 3.0.16, at assets/js/phone-input.js

285 lines 9.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Yatra international phone input.
3 *
4 * Enhances every `[data-yatra-phone]` control rendered by
5 * yatra_render_form_field(): a searchable country selector (flag + dial code)
6 * on the left, the national number on the right. Pasting a full international
7 * number ("+9779806015400") auto-selects the country (Nepal) and splits the
8 * number. A hidden `<field>_country` input carries the chosen ISO; the server
9 * combines dial code + national number on submit, so this script degrades
10 * gracefully (no JS → the plain number + default country still submit).
11 *
12 * Pure vanilla JS, no dependencies. Country data comes from
13 * window.yatraPhoneData.countries ([{iso,name,dial}]) +
14 * window.yatraPhoneData.priority ({dial: iso}).
15 */
16 (function () {
17 "use strict";
18
19 var DATA = window.yatraPhoneData || {};
20 var COUNTRIES = Array.isArray(DATA.countries) ? DATA.countries : [];
21 var PRIORITY = DATA.priority || {};
22 var I18N = DATA.i18n || {};
23 var SEARCH_PLACEHOLDER = I18N.search || "Search country";
24 var NO_RESULTS = I18N.noResults || "No matches";
25
26 if (!COUNTRIES.length) {
27 return; // nothing to build without country data
28 }
29
30 // iso (lowercase) -> {iso,name,dial}
31 var BY_ISO = {};
32 COUNTRIES.forEach(function (c) {
33 BY_ISO[String(c.iso).toLowerCase()] = c;
34 });
35
36 /** Detect country from a raw value that starts with "+". Mirrors FormatHelper::detectPhoneCountry. */
37 function detectCountry(raw) {
38 raw = (raw || "").trim();
39 if (!raw || raw.charAt(0) !== "+") return null;
40 var digits = raw.replace(/\D+/g, "");
41 if (!digits) return null;
42 for (var len = Math.min(4, digits.length); len >= 1; len--) {
43 var prefix = digits.slice(0, len);
44 var matches = COUNTRIES.filter(function (c) {
45 return c.dial === prefix;
46 });
47 if (matches.length) {
48 var iso = PRIORITY[prefix] || matches[0].iso;
49 return { iso: iso, dial: prefix, national: digits.slice(len) };
50 }
51 }
52 return null;
53 }
54
55 function flagUrl(base, iso) {
56 return base + String(iso).toLowerCase() + ".svg";
57 }
58
59 function closeAll(except) {
60 document
61 .querySelectorAll(".yatra-phone-field.is-open")
62 .forEach(function (f) {
63 if (f !== except) {
64 f.classList.remove("is-open");
65 var btn = f.querySelector(".yatra-phone-country");
66 if (btn) btn.setAttribute("aria-expanded", "false");
67 }
68 });
69 }
70
71 function initField(field) {
72 if (!field || field.dataset.yatraPhoneReady === "1") return;
73 field.dataset.yatraPhoneReady = "1";
74
75 var button = field.querySelector(".yatra-phone-country");
76 var flagImg = field.querySelector(".yatra-phone-flag");
77 var dialSpan = field.querySelector(".yatra-phone-dial");
78 var numberInput = field.querySelector(".yatra-phone-number");
79 var isoInput = field.querySelector(".yatra-phone-iso");
80 var flagBase = field.getAttribute("data-flag-base") || "";
81 if (!button || !numberInput || !isoInput) return;
82
83 var dropdown = null;
84 var listEl = null;
85 var searchEl = null;
86
87 function setCountry(iso) {
88 var c = BY_ISO[String(iso).toLowerCase()];
89 if (!c) return;
90 isoInput.value = c.iso;
91 if (dialSpan) dialSpan.textContent = "+" + c.dial;
92 if (flagImg) {
93 flagImg.setAttribute("src", flagUrl(flagBase, c.iso));
94 flagImg.setAttribute("alt", c.name);
95 }
96 // reflect selection in an open list
97 if (listEl) {
98 listEl
99 .querySelectorAll(".yatra-phone-option.is-active")
100 .forEach(function (o) {
101 o.classList.remove("is-active");
102 });
103 var active = listEl.querySelector(
104 '.yatra-phone-option[data-iso="' + c.iso + '"]',
105 );
106 if (active) active.classList.add("is-active");
107 }
108 }
109
110 function buildDropdown() {
111 if (dropdown) return;
112 dropdown = document.createElement("div");
113 dropdown.className = "yatra-phone-dropdown";
114
115 var searchWrap = document.createElement("div");
116 searchWrap.className = "yatra-phone-search-wrap";
117 searchEl = document.createElement("input");
118 searchEl.type = "text";
119 searchEl.className = "yatra-phone-search";
120 searchEl.setAttribute("placeholder", SEARCH_PLACEHOLDER);
121 searchEl.setAttribute("autocomplete", "off");
122 searchEl.setAttribute("aria-label", SEARCH_PLACEHOLDER);
123 searchWrap.appendChild(searchEl);
124
125 listEl = document.createElement("ul");
126 listEl.className = "yatra-phone-list";
127 listEl.setAttribute("role", "listbox");
128
129 COUNTRIES.forEach(function (c) {
130 var li = document.createElement("li");
131 li.className = "yatra-phone-option";
132 li.setAttribute("role", "option");
133 li.setAttribute("data-iso", c.iso);
134 li.setAttribute(
135 "data-search",
136 (c.name + " " + c.dial + " " + c.iso).toLowerCase(),
137 );
138 li.innerHTML =
139 '<img class="yatra-phone-flag" loading="lazy" width="22" height="16" alt="" src="' +
140 flagUrl(flagBase, c.iso) +
141 '">' +
142 '<span class="yatra-phone-option-name"></span>' +
143 '<span class="yatra-phone-option-dial">+' +
144 c.dial +
145 "</span>";
146 li.querySelector(".yatra-phone-option-name").textContent = c.name;
147 li.addEventListener("click", function () {
148 setCountry(c.iso);
149 close();
150 numberInput.focus();
151 });
152 listEl.appendChild(li);
153 });
154
155 dropdown.appendChild(searchWrap);
156 dropdown.appendChild(listEl);
157 field.appendChild(dropdown);
158
159 searchEl.addEventListener("input", function () {
160 var q = searchEl.value.trim().toLowerCase();
161 var anyVisible = false;
162 listEl.querySelectorAll(".yatra-phone-option").forEach(function (o) {
163 var match = !q || o.getAttribute("data-search").indexOf(q) !== -1;
164 o.style.display = match ? "" : "none";
165 if (match) anyVisible = true;
166 });
167 var empty = listEl.querySelector(".yatra-phone-empty");
168 if (!anyVisible) {
169 if (!empty) {
170 empty = document.createElement("li");
171 empty.className = "yatra-phone-empty";
172 empty.textContent = NO_RESULTS;
173 listEl.appendChild(empty);
174 }
175 empty.style.display = "";
176 } else if (empty) {
177 empty.style.display = "none";
178 }
179 });
180
181 searchEl.addEventListener("keydown", function (e) {
182 if (e.key === "Escape") {
183 close();
184 button.focus();
185 }
186 });
187 }
188
189 function open() {
190 buildDropdown();
191 closeAll(field);
192 field.classList.add("is-open");
193 button.setAttribute("aria-expanded", "true");
194 setCountry(isoInput.value); // highlight current
195 if (searchEl) {
196 searchEl.value = "";
197 searchEl.dispatchEvent(new Event("input"));
198 searchEl.focus();
199 }
200 // ensure active option is visible
201 if (listEl) {
202 var active = listEl.querySelector(".yatra-phone-option.is-active");
203 if (active) active.scrollIntoView({ block: "nearest" });
204 }
205 }
206
207 function close() {
208 field.classList.remove("is-open");
209 button.setAttribute("aria-expanded", "false");
210 }
211
212 button.addEventListener("click", function (e) {
213 e.preventDefault();
214 if (field.classList.contains("is-open")) {
215 close();
216 } else {
217 open();
218 }
219 });
220
221 // Live flag feedback while typing an international number; final split on
222 // blur/paste so digits don't jump around mid-keystroke.
223 numberInput.addEventListener("input", function () {
224 var v = numberInput.value;
225 if (v.charAt(0) === "+") {
226 var d = detectCountry(v);
227 if (d) setCountry(d.iso);
228 }
229 });
230
231 function splitIfInternational() {
232 var v = numberInput.value.trim();
233 if (v.charAt(0) !== "+") return;
234 var d = detectCountry(v);
235 if (d) {
236 setCountry(d.iso);
237 numberInput.value = d.national;
238 }
239 }
240 numberInput.addEventListener("blur", splitIfInternational);
241 numberInput.addEventListener("paste", function () {
242 setTimeout(splitIfInternational, 0);
243 });
244 }
245
246 function initAll(root) {
247 (root || document)
248 .querySelectorAll("[data-yatra-phone]")
249 .forEach(initField);
250 }
251
252 // Close on outside click / Escape.
253 document.addEventListener("click", function (e) {
254 if (!e.target.closest(".yatra-phone-field")) closeAll(null);
255 });
256 document.addEventListener("keydown", function (e) {
257 if (e.key === "Escape") closeAll(null);
258 });
259
260 // Initial + dynamically-added traveler rows (booking.js clones them).
261 if (document.readyState === "loading") {
262 document.addEventListener("DOMContentLoaded", function () {
263 initAll(document);
264 });
265 } else {
266 initAll(document);
267 }
268 if (typeof MutationObserver !== "undefined") {
269 var mo = new MutationObserver(function (muts) {
270 muts.forEach(function (m) {
271 m.addedNodes &&
272 m.addedNodes.forEach(function (n) {
273 if (n.nodeType !== 1) return;
274 if (n.matches && n.matches("[data-yatra-phone]")) initField(n);
275 initAll(n);
276 });
277 });
278 });
279 mo.observe(document.body, { childList: true, subtree: true });
280 }
281
282 // Expose for explicit re-init if a theme/integration needs it.
283 window.yatraInitPhoneInputs = initAll;
284 })();
285