PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.2.6
Yatra – Travel Booking & Tour Operator Software v3.0.2.6
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 / listing.js

listing.js in Yatra – Travel Booking & Tour Operator Software 3.0.2.6, at assets/js/listing.js

380 lines 14.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Listing Pages JavaScript
3 * Handles filter sidebar interactions and view toggles
4 *
5 * @package Yatra
6 */
7
8 (function() {
9 'use strict';
10
11 // Initialize when DOM is ready
12 if (document.readyState === 'loading') {
13 document.addEventListener('DOMContentLoaded', init);
14 } else {
15 init();
16 }
17
18 function init() {
19 initFilterToggles();
20 initViewToggle();
21 initPriceRange();
22 initAdvancedSearch();
23 initTravelersSelect();
24 // Search functionality moved to shortcode-specific JS
25 // initHorizontalSearchDropdowns();
26 // initDurationSlider();
27 }
28
29 // Advanced search toggle
30 function initAdvancedSearch() {
31 const toggleBtn = document.getElementById('advancedToggle');
32 const advancedOptions = document.getElementById('advancedOptions');
33
34 if (toggleBtn && advancedOptions) {
35 toggleBtn.addEventListener('click', function() {
36 const isOpen = advancedOptions.style.display !== 'none';
37 advancedOptions.style.display = isOpen ? 'none' : 'block';
38 toggleBtn.classList.toggle('active');
39 });
40 }
41 }
42
43 // Travelers selector
44 function initTravelersSelect() {
45 const travelersSelect = document.getElementById('travelersSelect');
46 if (!travelersSelect) return;
47
48 // Create dropdown for travelers
49 const dropdown = document.createElement('div');
50 dropdown.className = 'yatra-travelers-dropdown';
51 dropdown.style.cssText = 'display: none; position: absolute; top: 100%; left: 0; right: 0; background: #fff; border: 1px solid #e5e7eb; border-radius: 8px; margin-top: 4px; padding: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); z-index: 1000;';
52
53 dropdown.innerHTML = `
54 <div style="margin-bottom: 12px;">
55 <label style="display: block; font-size: 13px; font-weight: 600; color: #374151; margin-bottom: 8px;">Adults</label>
56 <div style="display: flex; align-items: center; gap: 12px;">
57 <button type="button" class="yatra-quantity-btn" data-action="decrease" style="width: 32px; height: 32px; border: 1px solid #d1d5db; background: #fff; border-radius: 6px; cursor: pointer; display: flex; align-items: center; justify-content: center;">-</button>
58 <span class="yatra-quantity-value" style="min-width: 30px; text-align: center; font-weight: 600;">2</span>
59 <button type="button" class="yatra-quantity-btn" data-action="increase" style="width: 32px; height: 32px; border: 1px solid #d1d5db; background: #fff; border-radius: 6px; cursor: pointer; display: flex; align-items: center; justify-content: center;">+</button>
60 </div>
61 </div>
62 <button type="button" class="yatra-apply-travelers" style="width: 100%; padding: 10px; background: #3b82f6; color: #fff; border: none; border-radius: 6px; font-weight: 600; cursor: pointer;">Apply</button>
63 `;
64
65 const parent = travelersSelect.parentElement;
66 parent.style.position = 'relative';
67 parent.appendChild(dropdown);
68
69 const input = travelersSelect.querySelector('input');
70 let adults = 2;
71
72 travelersSelect.addEventListener('click', function(e) {
73 e.stopPropagation();
74 dropdown.style.display = dropdown.style.display === 'none' ? 'block' : 'none';
75 });
76
77 dropdown.querySelectorAll('.yatra-quantity-btn').forEach(btn => {
78 btn.addEventListener('click', function(e) {
79 e.stopPropagation();
80 const action = this.getAttribute('data-action');
81 if (action === 'increase') {
82 adults = Math.min(adults + 1, 20);
83 } else {
84 adults = Math.max(adults - 1, 1);
85 }
86 dropdown.querySelector('.yatra-quantity-value').textContent = adults;
87 });
88 });
89
90 dropdown.querySelector('.yatra-apply-travelers').addEventListener('click', function(e) {
91 e.stopPropagation();
92 input.value = adults + ' Adult' + (adults > 1 ? 's' : '');
93 dropdown.style.display = 'none';
94 });
95
96 document.addEventListener('click', function() {
97 dropdown.style.display = 'none';
98 });
99
100 dropdown.addEventListener('click', function(e) {
101 e.stopPropagation();
102 });
103 }
104
105 // Filter section toggles
106 function initFilterToggles() {
107 const filterTitles = document.querySelectorAll('.yatra-filter-title');
108
109 filterTitles.forEach(title => {
110 title.addEventListener('click', function() {
111 const section = this.closest('.yatra-filter-section');
112 section.classList.toggle('open');
113 });
114 });
115
116 // Open all filter sections by default
117 document.querySelectorAll('.yatra-filter-section').forEach(section => {
118 section.classList.add('open');
119 });
120 }
121
122 // View toggle (grid/list): one grid per page; localStorage key is per grid id (not global).
123 function initViewToggle() {
124 const grids = [
125 document.getElementById('trip-grid'),
126 document.getElementById('category-grid'),
127 document.getElementById('destination-grid'),
128 document.getElementById('activity-grid')
129 ].filter(function (el) {
130 return el !== null;
131 });
132
133 if (grids.length === 0) {
134 return;
135 }
136
137 const grid = grids[0];
138 const storageKey = 'yatra_listing_view_' + (grid.id || 'listing');
139
140 const viewButtons = document.querySelectorAll('.yatra-view-btn[data-view]');
141 if (viewButtons.length === 0) {
142 return;
143 }
144
145 const listingRoot = grid.closest('.yatra-listing-page') || document.body;
146
147 function applyView(view) {
148 if (view !== 'list') {
149 view = 'grid';
150 }
151
152 listingRoot.querySelectorAll('.yatra-results-controls .yatra-view-btn[data-view]').forEach(function (btn) {
153 const btnView = btn.getAttribute('data-view');
154 btn.classList.toggle('active', btnView === view);
155 });
156
157 if (view === 'list') {
158 grid.classList.add('list-view');
159 grid.classList.remove('yatra-list-view');
160 } else {
161 grid.classList.remove('list-view');
162 grid.classList.remove('yatra-list-view');
163 }
164 }
165
166 let initialView = 'grid';
167 try {
168 const saved = window.localStorage.getItem(storageKey);
169 if (saved === 'list' || saved === 'grid') {
170 initialView = saved;
171 }
172 } catch (e) {
173 // ignore
174 }
175
176 applyView(initialView);
177
178 viewButtons.forEach(function (button) {
179 button.addEventListener('click', function () {
180 const view = this.getAttribute('data-view') || 'grid';
181 applyView(view);
182 try {
183 window.localStorage.setItem(storageKey, view);
184 } catch (e) {
185 // ignore
186 }
187 });
188 });
189 }
190
191 // Price range slider - use specific IDs to avoid conflict with duration slider
192 function initPriceRange() {
193 const minRange = document.getElementById('priceRangeMin');
194 const maxRange = document.getElementById('priceRangeMax');
195 const priceDisplay = document.querySelector('.yatra-price-display');
196 const minInput = document.getElementById('priceMin');
197 const maxInput = document.getElementById('priceMax');
198
199 if (!minRange || !maxRange || !priceDisplay) return;
200
201 function updatePriceDisplay() {
202 const min = parseInt(minRange.value) || 0;
203 const max = parseInt(maxRange.value) || 10000;
204
205 // Format with commas
206 const minFormatted = min.toLocaleString();
207 const maxFormatted = max.toLocaleString();
208
209 priceDisplay.textContent = `$${minFormatted} - $${maxFormatted}`;
210
211 if (minInput) minInput.value = min;
212 if (maxInput) maxInput.value = max;
213 }
214
215 minRange.addEventListener('input', function() {
216 if (parseInt(this.value) > parseInt(maxRange.value)) {
217 this.value = maxRange.value;
218 }
219 updatePriceDisplay();
220 });
221
222 maxRange.addEventListener('input', function() {
223 if (parseInt(this.value) < parseInt(minRange.value)) {
224 this.value = minRange.value;
225 }
226 updatePriceDisplay();
227 });
228
229 if (minInput) {
230 minInput.addEventListener('input', function() {
231 minRange.value = this.value || 0;
232 updatePriceDisplay();
233 });
234 }
235
236 if (maxInput) {
237 maxInput.addEventListener('input', function() {
238 maxRange.value = this.value || 10000;
239 updatePriceDisplay();
240 });
241 }
242
243 // Initial display
244 updatePriceDisplay();
245 }
246
247 // Clear filters functionality is handled by listing-filters.js
248 // Removed conflicting event listener to prevent interference
249
250 // Horizontal Search Dropdowns
251 function initHorizontalSearchDropdowns() {
252 const dropdowns = document.querySelectorAll('.yatra-search-dropdown');
253
254 if (dropdowns.length === 0) return;
255
256 // Close all dropdowns when clicking outside
257 document.addEventListener('click', function(e) {
258 if (!e.target.closest('.yatra-search-dropdown')) {
259 dropdowns.forEach(dropdown => {
260 dropdown.classList.remove('open');
261 });
262 }
263 });
264
265 dropdowns.forEach(dropdown => {
266 const trigger = dropdown.querySelector('.yatra-dropdown-trigger');
267 const menu = dropdown.querySelector('.yatra-dropdown-menu');
268 const options = dropdown.querySelectorAll('.yatra-dropdown-option');
269 const valueSpan = dropdown.querySelector('.yatra-dropdown-value');
270
271 if (trigger) {
272 trigger.addEventListener('click', function(e) {
273 e.stopPropagation();
274
275 // Close other dropdowns
276 dropdowns.forEach(d => {
277 if (d !== dropdown) {
278 d.classList.remove('open');
279 }
280 });
281
282 // Toggle current dropdown
283 dropdown.classList.toggle('open');
284 });
285 }
286
287 // Handle option selection
288 options.forEach(option => {
289 option.addEventListener('click', function(e) {
290 e.stopPropagation();
291
292 // Remove selected from siblings
293 options.forEach(opt => opt.classList.remove('selected'));
294
295 // Add selected to clicked option
296 this.classList.add('selected');
297
298 // Update the display value
299 if (valueSpan) {
300 valueSpan.textContent = this.textContent;
301 valueSpan.classList.add('selected');
302 }
303
304 // Close dropdown
305 dropdown.classList.remove('open');
306 });
307 });
308
309 // Keep dropdown open when clicking inside (for duration slider)
310 if (menu) {
311 menu.addEventListener('click', function(e) {
312 e.stopPropagation();
313 });
314 }
315 });
316 }
317
318 // Duration Slider
319 function initDurationSlider() {
320 const durationDropdown = document.querySelector('[data-dropdown="duration"]');
321 if (!durationDropdown) return;
322
323 const minSlider = durationDropdown.querySelector('#durationMin');
324 const maxSlider = durationDropdown.querySelector('#durationMax');
325 const minBadge = durationDropdown.querySelector('.yatra-duration-min-badge');
326 const maxBadge = durationDropdown.querySelector('.yatra-duration-max-badge');
327 const sliderRange = durationDropdown.querySelector('.yatra-slider-range');
328 const valueSpan = durationDropdown.querySelector('.yatra-dropdown-value');
329
330 if (!minSlider || !maxSlider) return;
331
332 function durationBounds() {
333 const lo = parseInt(durationDropdown.getAttribute('data-duration-min') || minSlider.min, 10);
334 const hi = parseInt(durationDropdown.getAttribute('data-duration-max') || maxSlider.max, 10);
335 const safeLo = isNaN(lo) ? 1 : lo;
336 const safeHi = isNaN(hi) || hi < safeLo ? safeLo : hi;
337 return { lo: safeLo, hi: safeHi, span: Math.max(1, safeHi - safeLo) };
338 }
339
340 function updateDurationDisplay() {
341 let min = parseInt(minSlider.value, 10);
342 let max = parseInt(maxSlider.value, 10);
343
344 // Ensure min doesn't exceed max
345 if (min > max) {
346 [min, max] = [max, min];
347 minSlider.value = min;
348 maxSlider.value = max;
349 }
350
351 // Update badges
352 if (minBadge) minBadge.textContent = min + ' Days';
353 if (maxBadge) maxBadge.textContent = max + ' Days';
354
355 // Update the dropdown value display
356 if (valueSpan) {
357 valueSpan.textContent = min + ' - ' + max + ' Days';
358 valueSpan.classList.add('selected');
359 }
360
361 // Update the slider range track
362 if (sliderRange) {
363 const b = durationBounds();
364 const minPercent = ((min - b.lo) / b.span) * 100;
365 const maxPercent = ((max - b.lo) / b.span) * 100;
366 sliderRange.style.left = minPercent + '%';
367 sliderRange.style.width = Math.max(0, maxPercent - minPercent) + '%';
368 }
369 }
370
371 minSlider.addEventListener('input', updateDurationDisplay);
372 maxSlider.addEventListener('input', updateDurationDisplay);
373
374 // Initial display
375 updateDurationDisplay();
376 }
377
378 })();
379
380