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 / single-trip-sidebar.js

single-trip-sidebar.js in Yatra – Travel Booking & Tour Operator Software 3.0.16, at assets/js/single-trip-sidebar.js

381 lines 15.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 // Parse a numeric min/max attribute while preserving a legitimate 0.
2 // `parseInt(el.getAttribute('max')) || 99` treated max="0" — what a sold-out
3 // departure renders — as "unset" and fell back to the placeholder ceiling, so
4 // the guest selector stayed usable and ignored the departure's real capacity.
5 // Only an absent or non-numeric value may fall back. Installed once and shared
6 // by every frontend script, so load order does not matter.
7 if (typeof window.yatraNumOr !== 'function') {
8 window.yatraNumOr = function (raw, fallback) {
9 var parsed = parseInt(raw, 10);
10 return isNaN(parsed) ? fallback : parsed;
11 };
12 }
13
14 function yatraScrollToBooking() {
15 const bookingForm = document.querySelector('.yatra-booking-form');
16 if (bookingForm) {
17 bookingForm.scrollIntoView({ behavior: 'smooth', block: 'center' });
18 } else {
19 // Fallback to main sidebar
20 const sidebar = document.querySelector('.yatra-trip-sidebar');
21 if (sidebar) {
22 sidebar.scrollIntoView({ behavior: 'smooth', block: 'start' });
23 }
24 }
25 }
26
27 function yatraScrollToContact() {
28 const contactSection = document.querySelector('#contact') || document.querySelector('.yatra-contact-form');
29 if (contactSection) {
30 contactSection.scrollIntoView({ behavior: 'smooth', block: 'center' });
31 }
32 }
33
34
35 function yatraCloseMobileSidebar() {
36 const sidebar = document.getElementById('yatra-mobile-sticky-sidebar');
37 if (sidebar) {
38 sidebar.classList.add('user-closed');
39
40 // Save closed state in sessionStorage (persists until page reload)
41 sessionStorage.setItem('yatra-mobile-sidebar-closed', 'true');
42
43 // Hide sidebar with animation
44 setTimeout(() => {
45 sidebar.style.transform = 'translateY(100%)';
46 sidebar.style.opacity = '0';
47 }, 100);
48 }
49 }
50
51 // Auto-show on page load (reset closed state)
52 document.addEventListener('DOMContentLoaded', function() {
53 const sidebar = document.getElementById('yatra-mobile-sticky-sidebar');
54
55 if (sidebar) {
56 // Clear any previous closed state on page load
57 sessionStorage.removeItem('yatra-mobile-sidebar-closed');
58
59 // Ensure sidebar is visible on page load (CSS will handle responsive)
60 sidebar.classList.remove('user-closed');
61 sidebar.classList.remove('hidden');
62 }
63
64 // Initialize mobile booking form functionality
65 initializeMobileBookingForm();
66 });
67
68 function initializeMobileBookingForm() {
69 // Mobile quantity controls
70 const mobileMinusBtns = document.querySelectorAll('.yatra-mobile-quantity-minus');
71 const mobilePlusBtns = document.querySelectorAll('.yatra-mobile-quantity-plus');
72
73 mobileMinusBtns.forEach(btn => {
74 btn.addEventListener('click', function() {
75 const targetId = this.getAttribute('data-target');
76 const input = document.getElementById(targetId);
77 if (input) {
78 const current = parseInt(input.value) || 1;
79 const min = window.yatraNumOr(input.getAttribute('min'), 1);
80 if (current > min) {
81 input.value = current - 1;
82 updateMobileTotal();
83 }
84 }
85 });
86 });
87
88 mobilePlusBtns.forEach(btn => {
89 btn.addEventListener('click', function() {
90 const targetId = this.getAttribute('data-target');
91 const input = document.getElementById(targetId);
92 if (input) {
93 const current = parseInt(input.value) || 1;
94 const max = window.yatraNumOr(input.getAttribute('max'), 20);
95 if (current < max) {
96 input.value = current + 1;
97 updateMobileTotal();
98 }
99 }
100 });
101 });
102
103 // Mobile date picker - Initialize exactly like main date picker
104 const mobileDateInput = document.getElementById('mobile_travel_date');
105 if (mobileDateInput && mobileDateInput.tagName === 'SELECT') {
106 // Pro "date-as-dropdown" mode: native <select>. Mirror selection to the
107 // desktop counterpart (kept off-screen on mobile) so the rest of the
108 // booking flow — which reads #travel_date — picks the same value.
109 mobileDateInput.addEventListener('change', function () {
110 var desktop = document.getElementById('travel_date');
111 if (desktop) {
112 if (desktop.tagName === 'SELECT') {
113 desktop.value = mobileDateInput.value;
114 desktop.dispatchEvent(new Event('change', { bubbles: true }));
115 } else if (desktop._flatpickr) {
116 desktop._flatpickr.setDate(mobileDateInput.value, true);
117 } else {
118 desktop.value = mobileDateInput.value;
119 }
120 }
121 updateMobileTotal();
122 });
123 } else if (mobileDateInput) {
124 // Default value: today (desktop behavior). If availability mode doesn't allow today,
125 // we will swap to the first enabled date after Flatpickr initializes.
126 const today = new Date();
127 today.setHours(0, 0, 0, 0);
128 const todayStr = today.getFullYear() + '-' +
129 String(today.getMonth() + 1).padStart(2, '0') + '-' +
130 String(today.getDate()).padStart(2, '0');
131 if (!mobileDateInput.value) {
132 mobileDateInput.value = todayStr;
133 }
134
135 // Check if Flatpickr is available
136 if (typeof flatpickr === 'undefined') {
137 // Make it a regular date input as fallback
138 mobileDateInput.type = 'date';
139 mobileDateInput.removeAttribute('readonly');
140 } else {
141 // Get availability dates from global trip data with fallback
142 let availabilityDates = [];
143 try {
144 if (window.yatraTripData && Array.isArray(window.yatraTripData.availabilityDates)) {
145 availabilityDates = window.yatraTripData.availabilityDates;
146 }
147 } catch (e) {
148 }
149
150 var flatpickrLocale = (window.yatraTripData && window.yatraTripData.flatpickrLocale)
151 ? window.yatraTripData.flatpickrLocale
152 : { firstDayOfWeek: 1 };
153 var mobileFpCommon = {
154 locale: flatpickrLocale,
155 clickOpens: true,
156 appendTo: document.body,
157 static: false,
158 onReady: function(selectedDates, dateStr, instance) {
159 if (instance.calendarContainer) {
160 instance.calendarContainer.style.zIndex = '100050';
161 }
162
163 // Ensure we have a selected date (desktop behavior).
164 // If current value is empty, use today.
165 if (!instance.selectedDates || instance.selectedDates.length === 0) {
166 instance.setDate(mobileDateInput.value || todayStr, true);
167 }
168 },
169 onChange: function(selectedDates, dateStr, instance) {
170 var mainDateInput = document.getElementById('travel_date');
171 if (mainDateInput && mainDateInput._flatpickr) {
172 mainDateInput._flatpickr.setDate(dateStr);
173 }
174 updateMobileTotal();
175 }
176 };
177 function yatraBindMobileDatepickerTouch(inputEl) {
178 if (!inputEl || !inputEl._flatpickr) {
179 return;
180 }
181 var fp = inputEl._flatpickr;
182 var btn = document.querySelector('.yatra-mobile-date-btn');
183 var fallbackWrap = inputEl.closest('.yatra-mobile-date-section');
184 var target = btn || fallbackWrap;
185 if (!target) return;
186 target.addEventListener('click', function(e) {
187 e.stopPropagation();
188 fp.open();
189 });
190 target.addEventListener('keydown', function(e) {
191 var key = e.key || e.code;
192 if (key === 'Enter' || key === ' ' || key === 'Spacebar') {
193 e.preventDefault();
194 e.stopPropagation();
195 fp.open();
196 }
197 });
198 }
199 // Initialize Flatpickr with same configuration as main date picker
200 var altFormat = (window.yatraTripData && (window.yatraTripData.flatpickrAltFormat || window.yatraTripData.altDateFormat))
201 ? (window.yatraTripData.flatpickrAltFormat || window.yatraTripData.altDateFormat)
202 : null;
203 if (!altFormat && window.yatraTripData && (window.yatraTripData.dateFormat || window.yatraTripData.date_format)) {
204 // Keep in sync with assets/js/trip.js helper (minimal mapping)
205 var php = window.yatraTripData.dateFormat || window.yatraTripData.date_format;
206 var map = { d:'d', j:'j', D:'D', l:'l', m:'m', n:'n', M:'M', F:'F', Y:'Y', y:'y' };
207 altFormat = '';
208 var esc = false;
209 for (var i = 0; i < php.length; i++) {
210 var ch = php[i];
211 if (esc) { altFormat += ch; esc = false; continue; }
212 if (ch === '\\\\') { esc = true; continue; }
213 altFormat += map[ch] || ch;
214 }
215 }
216 if (!altFormat) {
217 altFormat = 'F j, Y';
218 }
219
220 if (availabilityDates.length > 0) {
221 // Availability-based booking - enable only specific dates
222 flatpickr(mobileDateInput, Object.assign({}, mobileFpCommon, {
223 dateFormat: 'Y-m-d',
224 altInput: true,
225 altFormat: altFormat,
226 minDate: 'today',
227 enable: availabilityDates,
228 defaultDate: availabilityDates[0] || todayStr,
229 }));
230 } else {
231 // Regular booking - flexible dates with min/max constraints
232 var config = Object.assign({}, mobileFpCommon, {
233 dateFormat: 'Y-m-d',
234 altInput: true,
235 altFormat: altFormat,
236 minDate: 'today',
237 defaultDate: mobileDateInput.value || todayStr,
238 });
239 var minDate = mobileDateInput.getAttribute('data-min-date');
240 var maxDate = mobileDateInput.getAttribute('data-max-date');
241 if (minDate) {
242 config.minDate = minDate;
243 if (!mobileDateInput.value) {
244 config.defaultDate = minDate;
245 }
246 }
247 if (maxDate) {
248 config.maxDate = maxDate;
249 }
250 flatpickr(mobileDateInput, config);
251 }
252 yatraBindMobileDatepickerTouch(mobileDateInput);
253 }
254 }
255
256 // Travelers selector is rendered in the sticky bar using the same component as desktop.
257
258 // Mobile check availability button
259 const mobileCheckBtn = document.getElementById('mobile-check-availability-btn');
260 if (mobileCheckBtn) {
261 mobileCheckBtn.addEventListener('click', function() {
262 // Sync mobile form data with main form
263 syncMobileToMainForm();
264 // Trigger main check availability
265 const mainCheckBtn = document.getElementById('check-availability-btn');
266 if (mainCheckBtn) {
267 mainCheckBtn.click();
268 }
269 });
270 }
271 }
272
273 function syncMobileToMainForm() {
274 // Sync date
275 const mobileDate = document.getElementById('mobile_travel_date');
276 const mainDate = document.getElementById('travel_date');
277 if (mobileDate && mainDate) {
278 // If main date has Flatpickr instance, use it
279 if (mainDate._flatpickr) {
280 mainDate._flatpickr.setDate(mobileDate.value);
281 } else {
282 mainDate.value = mobileDate.value;
283 // Native <select> consumers (date-as-dropdown Pro mode) listen for
284 // `change` — emit one so downstream handlers wake up.
285 if (mainDate.tagName === 'SELECT') {
286 mainDate.dispatchEvent(new Event('change', { bubbles: true }));
287 }
288 }
289 }
290
291 // Sync travelers
292 const mobileTravelers = document.getElementById('mobile_num_travelers');
293 const mainTravelers = document.getElementById('num_travelers');
294 if (mobileTravelers && mainTravelers) {
295 mainTravelers.value = mobileTravelers.value;
296 }
297
298 // Sync traveler-based pricing (mobile selector -> desktop selector)
299 const mobileTravelerInputs = document.querySelectorAll('#mobile-quantity-selector input[id^="mobile_traveler_"]');
300 if (mobileTravelerInputs && mobileTravelerInputs.length > 0) {
301 mobileTravelerInputs.forEach((input) => {
302 const desktopId = input.id.replace(/^mobile_/, '');
303 const desktopInput = document.getElementById(desktopId);
304 if (desktopInput) {
305 desktopInput.value = input.value;
306 }
307 });
308 }
309 }
310
311 function updateMobileTotal() {
312 const sidebar = document.getElementById('yatra-mobile-sticky-sidebar');
313 if (!sidebar) return;
314
315 const pricingType = (window.yatraTripData && window.yatraTripData.pricingType) ? window.yatraTripData.pricingType : 'regular';
316 const mobileTotal = document.getElementById('mobile-total-amount');
317 if (!mobileTotal) return;
318
319 let total = 0;
320
321 if (pricingType === 'traveler_based') {
322 // Traveler-based pricing: sum all category quantities × prices
323 const travelerInputs = document.querySelectorAll('[id^="mobile_traveler_"]');
324 travelerInputs.forEach(input => {
325 const qty = parseInt(input.value) || 0;
326 const price = parseFloat(input.getAttribute('data-price')) || 0;
327 total += qty * price;
328 });
329 } else {
330 // Regular pricing: simple multiplication
331 const travelersInput = document.getElementById('mobile_num_travelers');
332 if (travelersInput) {
333 const travelers = parseInt(travelersInput.value) || 1;
334 const basePrice = parseFloat(travelersInput.getAttribute('data-price')) || 0;
335 total = travelers * basePrice;
336 }
337 }
338
339 // Format and update display (mobile total element has been removed)
340 // The mobile total price display is no longer shown
341 if (mobileTotal) {
342 if (window.yatraTripData && window.yatraTripData.currencySymbol) {
343 mobileTotal.textContent = window.yatraTripData.currencySymbol + total.toFixed(2);
344 } else {
345 mobileTotal.textContent = formatCurrency(total);
346 }
347 }
348
349 // Also sync with main form total if it exists (main total also removed)
350 const mainTotal = document.getElementById('total-amount');
351 if (mainTotal && window.yatraTripData && window.yatraTripData.currencySymbol) {
352 mainTotal.textContent = window.yatraTripData.currencySymbol + total.toFixed(2);
353 }
354 }
355
356 function formatCurrency(amount) {
357 // Simple currency formatting (can be enhanced)
358 return '$' + amount.toFixed(2);
359 }
360
361 // Auto-hide on scroll down, show on scroll up (also works for enquiry-only sticky)
362 let lastScrollTop = 0;
363 const mobileSidebar = document.getElementById('yatra-mobile-sticky-sidebar');
364 const scrollThreshold = 100;
365
366 window.addEventListener('scroll', function() {
367 if (!mobileSidebar) return;
368
369 const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
370
371 if (scrollTop > lastScrollTop && scrollTop > scrollThreshold) {
372 // Scrolling down - hide sidebar
373 mobileSidebar.classList.add('hidden');
374 } else {
375 // Scrolling up - show sidebar
376 mobileSidebar.classList.remove('hidden');
377 }
378
379 lastScrollTop = scrollTop <= 0 ? 0 : scrollTop;
380 }, false);
381