PageSourceSearch

https://www.extracity.co.zw/wp-content/plugins/extracityzw/assets/global/wbtm_global.js?ver=1790260137

js extracity.co.zw collected 2026-09-24 14:30:05 UTC 45,036 bytes, 1,080 lines download raw bytes

1//==================================================Search area==================//
2(function ($) {
3	"use strict";
4
5	/* Open a dynamically loaded jQuery UI date picker once its AJAX-injected
6	   initialization script has attached the widget. */
7	function wbtm_open_date_picker(input, attempts) {
8		attempts = typeof attempts === 'number' ? attempts : 12;
9		if (!input || !input.length) {
10			return;
11		}
12		if ($.fn.datepicker && input.hasClass('hasDatepicker')) {
13			window.setTimeout(function () {
14				input.trigger('focus');
15				input.datepicker('show');
16			}, 0);
17			return;
18		}
19		if (attempts > 0) {
20			window.setTimeout(function () {
21				wbtm_open_date_picker(input, attempts - 1);
22			}, 50);
23		}
24	}
25
26	$(document).on("click", "#wbtm_area button.get_wbtm_bus_list", function (e) {
27		e.preventDefault();
28		let searchArea = $(this).closest('.wbtm_search_area');
29		let parent = searchArea.closest('#wbtm_area');
30		let start = searchArea.find('input[name="bus_start_route"]').first();
31		let end = searchArea.find('input[name="bus_end_route"]').first();
32		let j_date = searchArea.find('input[name="j_date"]').first();
33		if (!mp_check_required(start)) {
34			start.trigger('click');
35			return false;
36		}
37		if (!mp_check_required(end)) {
38			end.trigger('click');
39			return false;
40		}
41		if (!mp_check_required(j_date)) {
42			j_date.siblings('input').focus();
43			return false;
44		} else {
45			let r_date = searchArea.find('input[name="r_date"]').first();
46			let isReturnSearch = searchArea.closest('#etc_panel_return').length > 0;
47			if (isReturnSearch && (!r_date.length || !r_date.val())) {
48				return false;
49			}
50			let post_id = searchArea.find('[name="wbtm_post_id"]').first().val();
51			$.ajax({
52				type: 'POST',
53				url: mp_ajax_url,
54				data: {
55					"action": "get_wbtm_bus_list",
56					"start_route": start.val(),
57					"end_route": end.val(),
58					"j_date": j_date.val(),
59					"r_date": r_date.length ? r_date.val() : '',
60					"post_id": post_id,
61				},
62				beforeSend: function () {
63					dLoader(parent);
64				},
65				success: function (data) {
66					parent.find('.wbtm_search_result').html(data).promise().done(function () {
67						dLoaderRemove(parent);
68						loadBgImage();
69					});
70				},
71				error: function (response) {
72					console.log(response);
73				}
74			});
75		}
76	});
77	$(document).on("click", "#wbtm_area button.wbtm_next_date", function () {
78		let date = $(this).data('date');
79		let parent = $(this).closest('#wbtm_area');
80		let searchArea = parent.find('.etc_tab_panel.etc_panel_active .wbtm_search_area').first();
81		if (!searchArea.length) { searchArea = parent.find('.wbtm_search_area:visible').first(); }
82		if (!searchArea.length) { searchArea = parent.find('.wbtm_search_area').first(); }
83		let name = $(this).closest('#wbtm_return_container').length > 0 ? 'r_date' : 'j_date';
84		searchArea.find('input[name=' + name + ']').first().val(date).trigger('change').promise().done(function () {
85			searchArea.find('.get_wbtm_bus_list').first().trigger('click');
86		});
87	});
88	$(document).on("mp_change", "div.wbtm_search_area .wbtm_start_point input.formControl", function () {
89		let current = $(this);
90		let start_route = current.val();
91		let parent = current.closest('.wbtm_search_area');
92		let target = parent.find('.wbtm_dropping_point');
93		parent.find('.wbtm_dropping_point .mp_input_select_list').remove();
94		target.find('input.formControl').val('');
95		dLoader_xs(parent);
96		let exit_route = 0;
97		parent.find('.wbtm_start_point .mp_input_select_list li').each(function () {
98			let current_route = $(this).data('value');
99			if (current_route === start_route) {
100				exit_route = 1;
101			}
102		}).promise().done(function () {
103			if (exit_route > 0) {
104				let post_id = parent.find('[name="wbtm_post_id"]').val();
105				$.ajax({
106					type: 'POST',
107					url: mp_ajax_url,
108					data: {
109						"action": "get_wbtm_dropping_point",
110						"start_route": start_route,
111						"post_id": post_id,
112					},
113					success: function (data) {
114						target.append(data).promise().done(function () {
115							wbtm_load_journey_date(parent, post_id, start_route);
116						}).promise().done(function () {
117							dLoaderRemove(parent);
118							target.find('input.formControl').trigger('click');
119						});
120					},
121					error: function (response) {
122						dLoaderRemove(parent);
123						target.html('<div class="_warningButton_xs">The seat layout could not be loaded. Please refresh and try again.</div>');
124						console.log(response);
125					}
126				});
127			} else {
128				dLoaderRemove(parent);
129				mp_alert(target);
130				current.val('').trigger('click');
131			}
132		});
133		//alert(start_route);
134	});
135	$(document).on("mp_change", "div.wbtm_search_area .wbtm_dropping_point input.formControl", function () {
136		let current = $(this);
137		let end_route = current.val();
138		let parent = current.closest('.wbtm_search_area');
139		let exit_route = 0;
140		parent.find('.wbtm_dropping_point .mp_input_select_list li').each(function () {
141			let current_route = $(this).data('value');
142			if (current_route === end_route) {
143				exit_route = 1;
144			}
145		}).promise().done(function () {
146			if (exit_route > 0) {
147				parent.find('input[name="j_date"]').siblings('input').focus();
148			} else {
149				current.val('').trigger('click');
150			}
151		});
152		//alert(start_route);
153	});
154	function wbtm_load_journey_date(parent, post_id, start_route) {
155		let target = parent.find('.wbtm_journey_date');
156		$.ajax({
157			type: 'POST',
158			url: mp_ajax_url,
159			data: {
160				"action": "get_wbtm_journey_date",
161				"start_route": start_route,
162				"post_id": post_id,
163				"picker_context": parent.closest('.etc_tab_panel').data('tab') || '',
164			},
165			success: function (data) {
166				target.html(data);
167			},
168			error: function (response) {
169				console.log(response);
170			}
171		});
172	}
173	$(document).on("change", "#wbtm_area input[name='j_date']", function () {
174		let date = $(this).val();
175		let parent = $(this).closest('.wbtm_search_area');
176		let target = parent.find('.wbtm_return_date');
177		if (target.length > 0 && date) {
178			let end_route = parent.find('input[name="bus_end_route"]').first().val();
179			let post_id = parent.find('[name="wbtm_post_id"]').first().val();
180			//alert(date);
181			//alert(end_route);
182			$.ajax({
183				type: 'POST',
184				url: mp_ajax_url,
185				data: {
186					"action": "get_wbtm_return_date",
187					"end_route": end_route,
188					"j_date": date,
189					"post_id": post_id,
190					"picker_context": parent.closest('.etc_tab_panel').data('tab') || '',
191				},
192				beforeSend: function () {
193					dLoader_xs(target);
194				},
195				success: function (data) {
196					target.html(data);
197					dLoaderRemove(target);
198
199					/* Return-tab flow: immediately continue from the selected
200					   departure date into the newly generated return picker. */
201					if (parent.closest('#etc_panel_return').length) {
202						var returnInput = target.find('.wbtm_return_date_input').first();
203						wbtm_open_date_picker(returnInput);
204					}
205				},
206				error: function (response) {
207					console.log(response);
208				}
209			});
210		}
211	});
212	$(document).on("click", "#wbtm_area .wbtm_journey_date_input", function () {
213		let parent = $(this).closest('.wbtm_search_area');
214		let start = parent.find('input[name="bus_start_route"]').first().val();
215		if (!start) {
216			mp_alert($(this));
217		}
218	});
219}(jQuery));
220//====================================================================//
221(function ($) {
222	"use strict";
223	$(document).on("click", "#get_wbtm_bus_details", function () {
224		let parent = $(this).closest('.wbtm_bus_list_area');
225		let post_id = $(this).attr('data-bus_id');
226		let target = parent.find('tr[data-row_id=' + post_id + ']').find('td.wbtm_bus_details');
227		if ($(this).hasClass('mActive')) {
228			target.find('>div').slideUp('fast');
229			mp_all_content_change($(this));
230		} else {
231			parent.find('#get_wbtm_bus_details.mActive').each(function () {
232				$(this).trigger('click');
233			});
234			let start = parent.find('input[name="wbtm_start_route"]').val();
235			let end = parent.find('input[name="wbtm_end_route"]').val();
236			let date = parent.find('input[name="wbtm_date"]').val();
237			if (start && end && date && post_id) {
238				$.ajax({
239					type: 'POST',
240					url: mp_ajax_url,
241					data: {
242						"action": "get_wbtm_bus_details",
243						"start_route": start,
244						"end_route": end,
245						"post_id": post_id,
246						"date": date,
247					},
248					beforeSend: function () {
249						dLoader(parent);
250					},
251					success: function (data) {
252						target.html(data);
253						dLoaderRemove(parent);
254						loadBgImage();
255					},
256					error: function (response) {
257						console.log(response);
258					}
259				});
260			}
261			mp_all_content_change($(this));
262		}
263	});
264}(jQuery));
265//====================================================================//
266(function ($) {
267	"use strict";
268	function wbtm_price_calculation(parent) {
269		let total_qty = wbtm_seat_qty(parent);
270		wbtm_seat_calculation(parent, total_qty);
271		wbtm_attendee_management(parent, total_qty);
272		let target_summary = parent.find('.wbtm_total');
273		let target_sub_total = parent.find('.wbtm_sub_total');
274		let total = wbtm_ticket_price(parent);
275		target_sub_total.html(mp_price_format(total));
276		if (total_qty > 0) {
277			parent.find('.wbtm_ex_service_area').slideDown('fast');
278			parent.find('.wbtm_form_submit_area').slideDown('fast');
279			total = total + wbtm_ex_service_price(parent);
280			target_summary.html(mp_price_format(total));
281		} else {
282			parent.find('.wbtm_ex_service_area').slideUp('fast');
283			parent.find('.wbtm_form_submit_area').slideUp('fast');
284			target_summary.html(mp_price_format(total));
285		}
286	}
287	function wbtm_ticket_price(parent) {
288		let total = 0;
289		if (parent.find('.wbtm_seat_plan_area').length > 0) {
290			parent.find('.seat_available.seat_selected').each(function () {
291				total = total + parseFloat($(this).attr('data-seat_price'));
292			});
293		} else {
294			parent.find('[name="wbtm_seat_qty[]"]').each(function () {
295				let qty = parseInt($(this).val());
296				let price = parseFloat($(this).attr('data-price'));
297				price = price && price >= 0 ? price : 0;
298				total = total + price * qty;
299			});
300		}
301		return total;
302	}
303	function wbtm_seat_qty(parent) {
304		let total_qty = 0;
305		if (parent.find('.wbtm_seat_plan_area').length > 0) {
306			parent.find('.seat_available.seat_selected').each(function () {
307				total_qty++;
308			});
309		} else {
310			parent.find('[name="wbtm_seat_qty[]"]').each(function () {
311				total_qty = total_qty + parseInt($(this).val());
312			});
313		}
314		return total_qty;
315	}
316	function wbtm_ex_service_price(parent) {
317		let total = 0
318		parent.find('[name="extra_service_qty[]"]').each(function () {
319			let ex_qty = parseInt($(this).val());
320			let ex_price = $(this).attr('data-price');
321			ex_price = ex_price && ex_price >= 0 ? ex_price : 0;
322			total = total + parseFloat(ex_price) * ex_qty;
323		});
324		return total;
325	}
326	$(document).on('change', '.wbtm_registration_area [name="wbtm_seat_qty[]"]', function () {
327		let parent = $(this).closest('.wbtm_registration_area');
328		wbtm_price_calculation(parent);
329	});
330	$(document).on('change', '.wbtm_registration_area [name="extra_service_qty[]"]', function () {
331		let parent = $(this).closest('.wbtm_registration_area');
332		wbtm_price_calculation(parent);
333	});
334	$(document).on('click', '.wbtm_registration_area .seat_available', function () {
335		let current = $(this);
336		let parent = current.closest('.wbtm_registration_area');
337		if (!current.hasClass('seat_selected')) {
338			wbtm_maybe_warn_non_optimal_seat(parent, current);
339		} else {
340			wbtm_clear_recommendation_notice(parent);
341		}
342		if (current.hasClass('seat_selected')) {
343			let target = current.closest('.mp_seat_item').find('.wbtm_seat_item_list li:first-child');
344			if (target.length > 0) {
345				let seat_label = target.attr('data-seat_label');
346				let seat_price = target.attr('data-seat_price');
347				let seat_type = target.attr('data-seat_type');
348				current.attr('data-seat_label', seat_label).attr('data-seat_price', seat_price).attr('data-seat_type', seat_type);
349			}
350		}
351		current.toggleClass('seat_selected').promise().done(function () {
352			wbtm_price_calculation(parent);
353		});
354	});
355	$(document).on('click', '.wbtm_registration_area .wbtm_seat_item_list li', function () {
356		let current = $(this);
357		let target = current.closest('.mp_seat_item').find('.seat_available');
358		let seat_label = current.attr('data-seat_label');
359		let seat_price = current.attr('data-seat_price');
360		let seat_type = current.attr('data-seat_type');
361		let parent = current.closest('.wbtm_registration_area');
362		target.attr('data-seat_label', seat_label).attr('data-seat_price', seat_price).attr('data-seat_type', seat_type).promise().done(function () {
363			if (target.hasClass('seat_selected')) {
364				wbtm_price_calculation(parent);
365			} else {
366				target.trigger('click');
367			}
368		});
369	});
370	$(document).on('click', '.wbtm_registration_area .wbtm_selected_seat_details .mp_item_remove', function () {
371		let current = $(this);
372		let current_tr = current.closest('tr');
373		let seat_name = current_tr.attr('data-seat_name');
374		let seat_type = current_tr.attr('data-seat_type');
375		let parent = current.closest('.wbtm_registration_area');
376		parent.find('.seat_available.seat_selected').each(function () {
377			if ($(this).attr('data-seat_name') === seat_name && $(this).attr('data-seat_type') === seat_type) {
378				$(this).trigger('click');
379				return false;
380			}
381		});
382	});
383	function wbtm_seat_calculation(parent, total_qty) {
384		if (parent.find('.wbtm_seat_plan_area').length > 0) {
385			let upper_area = parent.find('.wbtm_seat_plan_lower');
386			if (upper_area.length > 0) {
387				let upper_target = parent.find('[name="wbtm_selected_seat"]');
388				let upper_target_type = parent.find('[name="wbtm_selected_seat_type"]');
389				let seats = '';
390				let seats_type = '';
391				upper_area.find('.seat_available.seat_selected').each(function () {
392					seats = seats ? seats + ',' + $(this).attr('data-seat_name') : $(this).attr('data-seat_name');
393					seats_type = seats_type ? seats_type + ',' + $(this).attr('data-seat_type') : $(this).attr('data-seat_type');
394				}).promise().done(function () {
395					upper_target.val(seats);
396					upper_target_type.val(seats_type);
397				});
398			}
399			let lower_area = parent.find('.wbtm_seat_plan_upper');
400			if (lower_area.length > 0) {
401				let lower_target = parent.find('[name="wbtm_selected_seat_dd"]');
402				let lower_target_type = parent.find('[name="wbtm_selected_seat_dd_type"]');
403				let seats_dd = '';
404				let seats_dd_type = '';
405				lower_area.find('.seat_available.seat_selected').each(function () {
406					seats_dd = seats_dd ? seats_dd + ',' + $(this).attr('data-seat_name') : $(this).attr('data-seat_name');
407					seats_dd_type = seats_dd_type ? seats_dd_type + ',' + $(this).attr('data-seat_type') : $(this).attr('data-seat_type');
408				}).promise().done(function () {
409					lower_target.val(seats_dd);
410					lower_target_type.val(seats_dd_type);
411				});
412			}
413			wbtm_selected_seat_details(parent, total_qty)
414		}
415	}
416	function wbtm_selected_seat_details(parent, total_qty) {
417		if (parent.find('.wbtm_seat_plan_area').length > 0) {
418			let target = parent.find('.wbtm_selected_seat_details .wbtm_item_insert');
419			if (total_qty > 0) {
420				let item_length = target.find('.wbtm_remove_area').length;
421				if (item_length !== total_qty) {
422					let hidden_target_tr = parent.find('.wbtm_item_hidden .wbtm_remove_area');
423					parent.find('.seat_available.seat_selected').each(function () {
424						let seat_name = $(this).attr('data-seat_name');
425						if (target.find('[data-seat_name="' + seat_name + '"]').length === 0) {
426							let seat_label = $(this).attr('data-seat_label');
427							let seat_price = $(this).attr('data-seat_price');
428							let seat_type = $(this).attr('data-seat_type');
429							hidden_target_tr.attr('data-seat_type', seat_type).attr('data-seat_name', seat_name).promise().done(function () {
430								hidden_target_tr.find('.insert_seat_label').html(seat_label);
431								hidden_target_tr.find('.insert_seat_name').html(seat_name);
432								hidden_target_tr.find('.insert_seat_price').html(mp_price_format(seat_price));
433							}).promise().done(function () {
434								target.append(hidden_target_tr.clone());
435							});
436						}
437					}).promise().done(function () {
438						item_length = target.find('.wbtm_remove_area').length;
439						if (item_length !== total_qty) {
440							target.find('.wbtm_remove_area').each(function () {
441								let seat_name = $(this).attr('data-seat_name');
442								if (parent.find('.seat_available.seat_selected[data-seat_name="' + seat_name + '"]').length === 0) {
443									$(this).remove();
444								}
445							});
446						}
447					});
448				}
449			} else {
450				target.html('');
451			}
452		}
453	}
454
455	function wbtm_get_booking_mode(parent) {
456		var mode = parent.find('#wbtm_booking_mode').val() || 'normal';
457		return mode === 'seat_blocking' ? 'seat_blocking' : 'normal';
458	}
459
460	function wbtm_clear_duplicate_passenger_state(parent) {
461		parent.find('.wbtm-duplicate-passenger').removeClass('wbtm-duplicate-passenger');
462		var notice = parent.find('.wbtm-passenger-duplicate-notice');
463		if (notice.length) {
464			notice.hide().text('');
465		}
466	}
467
468
469
470	function wbtm_is_internal_recommendation_user(parent) {
471		return parent.find('.wbtm-seat-recommendation-notice').length > 0;
472	}
473	function wbtm_clear_recommendation_notice(parent) {
474		var notice = parent.find('.wbtm-seat-recommendation-notice');
475		if (notice.length) {
476			notice.stop(true, true).hide().text('');
477		}
478	}
479
480	function wbtm_show_recommendation_notice(parent, message) {
481		var notice = parent.find('.wbtm-seat-recommendation-notice');
482		if (notice.length) {
483			notice.text(message).stop(true, true).fadeIn(150);
484		}
485	}
486
487	function wbtm_get_best_available_recommendation(parent) {
488		var maxScore = 0;
489		parent.find('.seat_available').not('.seat_selected').each(function(){
490			var score = parseInt($(this).attr('data-recommendation-score'), 10);
491			if (!isNaN(score) && score > maxScore) {
492				maxScore = score;
493			}
494		});
495		return maxScore;
496	}
497
498	function wbtm_maybe_warn_non_optimal_seat(parent, current) {
499		if (!wbtm_is_internal_recommendation_user(parent)) {
500			return;
501		}
502		var currentScore = parseInt(current.attr('data-recommendation-score'), 10);
503		if (isNaN(currentScore)) {
504			currentScore = 0;
505		}
506		var bestAvailable = wbtm_get_best_available_recommendation(parent);
507		if (bestAvailable > currentScore) {
508			var label = bestAvailable >= 3 ? '★★★' : (bestAvailable === 2 ? '★★' : '★');
509			wbtm_show_recommendation_notice(parent, 'Better fit recommended seats are available for this trip (' + label + '). Using them helps preserve fully vacant seats for longer journeys.');
510		} else {
511			wbtm_clear_recommendation_notice(parent);
512		}
513	}
514
515	function wbtm_validate_duplicate_passengers(parent) {
516		if (wbtm_get_booking_mode(parent) === 'seat_blocking') {
517			wbtm_clear_duplicate_passenger_state(parent);
518			return true;
519		}
520		var rows = [];
521		var duplicates = [];
522		parent.find('.wbtm_attendee_area .wbtm_attendee_item').each(function(){
523			var item = $(this);
524			var first = $.trim(item.find('input[name="wbtm_first_name[]"]').val() || '');
525			var last = $.trim(item.find('input[name="wbtm_last_name[]"]').val() || '');
526			var phone = $.trim(item.find('input[name="wbtm_reg_phone[]"]').val() || '');
527			var key = first.toLowerCase() + '|' + last.toLowerCase() + '|' + phone.replace(/\D+/g, '');
528			rows.push({item:item, first:first, last:last, phone:phone, key:key});
529		});
530		wbtm_clear_duplicate_passenger_state(parent);
531		for (var i = 0; i < rows.length; i++) {
532			if (!rows[i].first || !rows[i].last || !rows[i].phone) { continue; }
533			for (var j = i + 1; j < rows.length; j++) {
534				if (rows[i].key && rows[i].key === rows[j].key) {
535					duplicates.push(rows[i].item, rows[j].item);
536				}
537			}
538		}
539		if (duplicates.length) {
540			$.each(duplicates, function(_, item){
541				item.find('input[name="wbtm_first_name[]"], input[name="wbtm_last_name[]"], input[name="wbtm_reg_phone[]"]').addClass('wbtm-duplicate-passenger');
542			});
543			var notice = parent.find('.wbtm-passenger-duplicate-notice');
544			if (notice.length) {
545				notice.text('Customer records cannot be identical.').show();
546			}
547			return false;
548		}
549		return true;
550	}
551
552	function wbtm_attendee_management(parent, total_qty) {
553		let form_target = parent.find('.wbtm_attendee_area');
554		if (wbtm_get_booking_mode(parent) === 'seat_blocking') {
555			form_target.html('').hide();
556			wbtm_clear_duplicate_passenger_state(parent);
557			wbtm_clear_recommendation_notice(parent);
558			return;
559		}
560		if (form_target.length > 0 && total_qty > 0) {
561			form_target.slideDown(250);
562			let form_length = form_target.find('.wbtm_attendee_item').length;
563			if (form_length !== total_qty) {
564				let hidden_target = parent.find('.wbtm_hidden_form');
565				if (parent.find('.wbtm_seat_plan_area').length > 0) {
566					parent.find('.seat_available.seat_selected').each(function () {
567						let seat_name = $(this).attr('data-seat_name');
568						if (form_target.find('[data-seat_name="' + seat_name + '"]').length === 0) {
569							hidden_target.find('.wbtm_attendee_item').attr('data-seat_name', seat_name);
570							hidden_target.find('.wbtm_seat_name').html(seat_name).promise().done(function () {
571								form_target.append(hidden_target.html());
572							}).promise().done(function () {
573								mp_load_date_picker(parent);
574							});
575						}
576					}).promise().done(function () {
577						form_length = form_target.find('.wbtm_attendee_item').length;
578						if (form_length !== total_qty) {
579							form_target.find('.wbtm_attendee_item').each(function () {
580								let seat_name = $(this).attr('data-seat_name');
581								if (parent.find('.seat_available.seat_selected[data-seat_name="' + seat_name + '"]').length === 0) {
582									$(this).remove();
583								}
584							});
585						}
586					});
587				} else {
588					if (form_length > total_qty) {
589						for (let i = form_length; i > total_qty; i--) {
590							form_target.find('.wbtm_attendee_item:last-child').slideUp(250).remove();
591						}
592					} else {
593						for (let i = form_length; i < total_qty; i++) {
594							hidden_target.find('.wbtm_seat_name').html(i + 1).promise().done(function () {
595								form_target.append(hidden_target.html());
596							}).promise().done(function () {
597								mp_load_date_picker(parent);
598							});
599						}
600					}
601				}
602			}
603		} else {
604			form_target.html('').slideUp(250);
605		}
606	}
607
608	$(document).on('change', '#wbtm_booking_mode', function(){
609		let parent = $(this).closest('.wbtm_registration_area, .wbtm_single_result, .wbtm_bus_details, form, body');
610		let selectedSeats = parent.find('.seat_available.seat_selected').length;
611		if (selectedSeats > 0 || parent.find('.wbtm_seat_plan_area').length > 0) {
612			wbtm_price_calculation(parent);
613		} else {
614			let total_qty = parseInt(parent.find('.wbtm_total_seat').text(), 10);
615			if (isNaN(total_qty)) {
616				total_qty = 0;
617			}
618			wbtm_attendee_management(parent, total_qty || 0);
619		}
620	});
621
622	$(document).on('input change', 'input[name="wbtm_first_name[]"], input[name="wbtm_last_name[]"], input[name="wbtm_reg_phone[]"]', function(){
623		let parent = $(this).closest('.wbtm_registration_area, .wbtm_single_result, .wbtm_bus_details, form, body');
624		wbtm_validate_duplicate_passengers(parent);
625	});
626
627	function wbtm_has_missing_attendee_forms(parent) {
628		if (wbtm_get_booking_mode(parent) === 'seat_blocking') {
629			return false;
630		}
631		let selectedSeats = parent.find('.seat_available.seat_selected').length;
632		if (selectedSeats < 1) {
633			return false;
634		}
635		return parent.find('.wbtm_attendee_area .wbtm_attendee_item').length < selectedSeats;
636	}
637
638	function wbtm_show_missing_attendee_notice(parent) {
639		let notice = parent.find('.wbtm-passenger-duplicate-notice');
640		if (notice.length) {
641			notice.text('Please complete passenger details for each selected seat.').show();
642		}
643	}
644
645	$(document).on('click', '.wbtm_form_submit_area button[name="add-to-cart"]', function(e){
646		let parent = $(this).closest('.wbtm_registration_area, .wbtm_single_result, .wbtm_bus_details, form');
647		if (parent.length && wbtm_has_missing_attendee_forms(parent)) {
648			wbtm_show_missing_attendee_notice(parent);
649			e.preventDefault();
650			return false;
651		}
652		if (parent.length && !wbtm_validate_duplicate_passengers(parent)) {
653			e.preventDefault();
654			return false;
655		}
656	});
657
658	$(document).on('submit', 'form.cart, form', function(e){
659		let form = $(this);
660		if (form.find('.wbtm_form_submit_area').length && wbtm_has_missing_attendee_forms(form)) {
661			wbtm_show_missing_attendee_notice(form);
662			e.preventDefault();
663			return false;
664		}
665		if (form.find('.wbtm_form_submit_area').length && !wbtm_validate_duplicate_passengers(form)) {
666			e.preventDefault();
667			return false;
668		}
669	});
670}(jQuery));
671//============================================================================Extracity round-trip coordinator================//
672(function ($) {
673	"use strict";
674
675	function etcMoneyToNumber(text) {
676		var raw = (text || '').toString().replace(/[^0-9.\-]+/g, '');
677		var num = parseFloat(raw);
678		return isNaN(num) ? 0 : num;
679	}
680	function etcFormatMoney(num) {
681		num = parseFloat(num);
682		if (isNaN(num)) { num = 0; }
683		return '$' + num.toFixed(2);
684	}
685	function etcEscape(text) {
686		return $('<div/>').text(text || '').html();
687	}
688	function etcGetArea(el) {
689		return $(el).closest('#wbtm_area');
690	}
691	function etcGetSearchScope(area) {
692		var scope = area.find('.etc_tab_panel.etc_panel_active .wbtm_search_area').first();
693		if (!scope.length) { scope = area.find('.wbtm_search_area:visible').first(); }
694		if (!scope.length) { scope = area.find('.wbtm_search_area').first(); }
695		return scope;
696	}
697	function etcGetSearchValues(area) {
698		var scope = etcGetSearchScope(area);
699		return {
700			post_id: scope.find('[name="wbtm_post_id"]').first().val() || '',
701			start_route: scope.find('input[name="bus_start_route"]').first().val() || '',
702			end_route: scope.find('input[name="bus_end_route"]').first().val() || '',
703			j_date: scope.find('input[name="j_date"]').first().val() || '',
704			r_date: scope.find('input[name="r_date"]').first().val() || ''
705		};
706	}
707	function etcEnsureHidden(form, name, value) {
708		var input = form.find('input[name="' + name + '"]');
709		if (!input.length) { input = $('<input type="hidden" />').attr('name', name).appendTo(form); }
710		input.val(value);
711	}
712	function etcSerializeFormWithButton(form, button) {
713		var data = form.serialize();
714		var btn = $(button);
715		if (btn.length && btn.attr('name') && btn.val()) {
716			data += (data ? '&' : '') + encodeURIComponent(btn.attr('name')) + '=' + encodeURIComponent(btn.val());
717		}
718		var product = form.find('input[name="etc_wc_product_id"]').val() || btn.val() || '';
719		if (product && data.indexOf('etc_wc_product_id=') === -1) {
720			data += (data ? '&' : '') + 'etc_wc_product_id=' + encodeURIComponent(product);
721		}
722		return data;
723	}
724	function etcLegSummaryFromForm(form) {
725		var seats = [];
726		var upper = form.find('input[name="wbtm_selected_seat"]').val() || '';
727		var deck = form.find('input[name="wbtm_selected_seat_dd"]').val() || '';
728		if (upper) { seats.push(upper); }
729		if (deck) { seats.push(deck); }
730		if (!seats.length) {
731			form.find('[name="wbtm_seat_qty[]"]').each(function(index){
732				var qty = parseInt($(this).val(), 10);
733				if (qty > 0) { seats.push('Qty ' + qty); }
734			});
735		}
736		return {
737			bp: form.find('input[name="wbtm_bp_place"]').val() || '',
738			dp: form.find('input[name="wbtm_dp_place"]').val() || '',
739			seats: seats.join(', '),
740			total: etcMoneyToNumber(form.find('.wbtm_total').text())
741		};
742	}
743	function etcDecodeSerializedForm(serialized) {
744		var data = {};
745		(serialized || '').split('&').forEach(function(pair){
746			if (!pair) { return; }
747			var parts = pair.split('=');
748			var key = decodeURIComponent((parts.shift() || '').replace(/\+/g, ' '));
749			var value = decodeURIComponent((parts.join('=') || '').replace(/\+/g, ' '));
750			if (!key) { return; }
751			if (!data[key]) { data[key] = []; }
752			data[key].push(value);
753		});
754		return data;
755	}
756	function etcPassengerLabel(passenger, index) {
757		var first = passenger.wbtm_first_name || '';
758		var last = passenger.wbtm_last_name || '';
759		var phone = passenger.wbtm_reg_phone || '';
760		var name = $.trim(first + ' ' + last);
761		if (!name && phone) { name = phone; }
762		if (!name) { name = 'Passenger ' + (index + 1); }
763		if (phone && name.indexOf(phone) === -1) { name += ' — ' + phone; }
764		return name;
765	}
766	function etcPassengersFromSerialized(serialized) {
767		var data = etcDecodeSerializedForm(serialized);
768		var fields = ['wbtm_first_name','wbtm_last_name','wbtm_reg_phone','wbtm_reg_email','wbtm_reg_address','wbtm_user_gender'];
769		var max = 0;
770		fields.forEach(function(field){
771			var values = data[field + '[]'] || data[field] || [];
772			if (values.length > max) { max = values.length; }
773		});
774		var passengers = [];
775		for (var i = 0; i < max; i++) {
776			var passenger = {};
777			var hasValue = false;
778			fields.forEach(function(field){
779				var values = data[field + '[]'] || data[field] || [];
780				var value = values[i] || '';
781				passenger[field] = value;
782				if ($.trim(value)) { hasValue = true; }
783			});
784			if (hasValue) {
785				passenger.label = etcPassengerLabel(passenger, passengers.length);
786				passengers.push(passenger);
787			}
788		}
789		return passengers;
790	}
791	function etcFillPassengerItem(item, passenger, clearWhenEmpty) {
792		var fields = ['wbtm_first_name','wbtm_last_name','wbtm_reg_phone','wbtm_reg_email','wbtm_reg_address','wbtm_user_gender'];
793		fields.forEach(function(field){
794			var target = item.find('[name="' + field + '[]"], [name="' + field + '"]').first();
795			if (!target.length) { return; }
796			if (!passenger && !clearWhenEmpty) { return; }
797			target.val(passenger ? (passenger[field] || '') : '').trigger('input').trigger('change');
798		});
799	}
800	function etcPassengerItemHasUserText(item) {
801		var hasText = false;
802		item.find('input[name^="wbtm_"], textarea[name^="wbtm_"], select[name^="wbtm_"]').each(function(){
803			if ($.trim($(this).val() || '')) { hasText = true; return false; }
804		});
805		return hasText;
806	}
807	function etcBuildPassengerReuseControl(item, passengers, autoIndex, sameCount) {
808		var control = item.find('.etc_return_passenger_reuse');
809		if (!control.length) {
810			control = $('<div class="etc_return_passenger_reuse"><label><span>Return passenger</span><select class="etc_return_passenger_select"></select></label><small></small></div>');
811			item.find('.mpPanelHeader').after(control);
812		}
813		var select = control.find('select');
814		var previous = select.val();
815		select.empty();
816		select.append('<option value="">Add / edit different passenger</option>');
817		$.each(passengers, function(index, passenger){
818			select.append($('<option/>').attr('value', String(index)).text(passenger.label || ('Passenger ' + (index + 1))));
819		});
820		if (sameCount && autoIndex >= 0 && autoIndex < passengers.length && !item.attr('data-etc-return-passenger-touched')) {
821			select.val(String(autoIndex));
822			control.find('small').text('Passenger details copied from the outbound trip. You can still edit them.');
823		} else if (previous && passengers[parseInt(previous, 10)]) {
824			select.val(previous);
825			control.find('small').text('Using details from the outbound trip. You can still edit them.');
826		} else {
827			select.val('');
828			control.find('small').text('Choose an outbound passenger or enter different details.');
829		}
830	}
831	function etcApplyReturnPassengerReuse(area, form) {
832		var state = area.data('etcRoundTripState') || null;
833		if (!state || !state.outboundPassengers || !state.outboundPassengers.length || !area.hasClass('etc_round_trip_return_stage')) { return; }
834		var items = form.find('.wbtm_attendee_area .wbtm_attendee_item');
835		if (!items.length) { return; }
836		var sameCount = items.length === state.outboundPassengers.length;
837		items.each(function(index){
838			var item = $(this);
839			var wasBlank = !etcPassengerItemHasUserText(item);
840			etcBuildPassengerReuseControl(item, state.outboundPassengers, index, sameCount);
841			if (sameCount && !item.attr('data-etc-return-passenger-filled') && wasBlank) {
842				etcFillPassengerItem(item, state.outboundPassengers[index], false);
843				item.attr('data-etc-return-passenger-filled', '1');
844			}
845		});
846	}
847	function etcScheduleReturnPassengerReuse(el) {
848		var area = etcGetArea(el);
849		if (!area.hasClass('etc_round_trip_return_stage')) { return; }
850		window.setTimeout(function(){
851			area.find('.wbtm_registration_area form').each(function(){
852				etcApplyReturnPassengerReuse(area, $(this));
853			});
854		}, 320);
855	}
856	function etcRenderPanel(area) {
857		var state = area.data('etcRoundTripState') || null;
858		var panel = area.find('.etc_round_trip_flow_panel');
859		if (!state) { panel.remove(); return; }
860		if (!panel.length) {
861			panel = $('<div class="etc_round_trip_flow_panel _dLayout_dShadow_1_mT" role="status"></div>');
862			etcGetSearchScope(area).after(panel);
863		}
864		var outbound = state.outboundSummary || {};
865		var ret = state.returnSummary || {};
866		var total = (parseFloat(outbound.total) || 0) + (parseFloat(ret.total) || 0);
867		var returnHtml = ret.bp ?
868			'<div class="etc_round_trip_leg"><span class="etc_leg_badge">Return</span><strong>' + etcEscape(ret.bp) + ' → ' + etcEscape(ret.dp) + '</strong><span>Seats: ' + etcEscape(ret.seats || 'Selected') + '</span><span>' + etcFormatMoney(ret.total || 0) + '</span></div>' :
869			'<div class="etc_round_trip_leg etc_pending"><span class="etc_leg_badge">Return</span><strong>Select return bus and seats next</strong><span>Return date: ' + etcEscape(state.search ? state.search.r_date : '') + '</span></div>';
870		panel.html(
871			'<div class="etc_round_trip_panel_head"><div><span class="etc_round_trip_kicker">Round trip booking</span><h4>Build both trips before checkout</h4></div><button type="button" class="etc_round_trip_edit_outbound">Edit outbound</button></div>' +
872			'<div class="etc_round_trip_legs">' +
873			'<div class="etc_round_trip_leg"><span class="etc_leg_badge">Outbound</span><strong>' + etcEscape(outbound.bp || '') + ' → ' + etcEscape(outbound.dp || '') + '</strong><span>Seats: ' + etcEscape(outbound.seats || 'Selected') + '</span><span>
873' + etcFormatMoney(outbound.total || 0) + '</span></div>' +
874			returnHtml + '</div>' +
875			'<div class="etc_round_trip_total"><span>Combined total</span><strong>' + etcFormatMoney(total) + '</strong></div>'
876		);
877	}
878	function etcResetRoundTrip(area) {
879		area.removeClass('etc_round_trip_return_stage etc_round_trip_active');
880		area.removeData('etcRoundTripState');
881		area.find('.etc_round_trip_flow_panel').remove();
882	}
883	function etcUpdateSubmitText(form) {
884		var area = etcGetArea(form);
885		var button = form.find('.etc_round_trip_submit');
886		if (!button.length) { return; }
887		var search = etcGetSearchValues(area);
888		var text = button.attr('data-single-text') || button.text();
889		if (area.hasClass('etc_round_trip_return_stage')) {
890			text = button.attr('data-final-text') || 'Proceed to Pay';
891		} else if (search.r_date) {
892			text = button.attr('data-return-text') || 'Continue to Return Trip';
893		}
894		button.text(text);
895	}
896	function etcLoadReturnResults(area) {
897		var state = area.data('etcRoundTripState');
898		if (!state || !state.search) { return; }
899		var search = state.search;
900		$.ajax({
901			type: 'POST', url: mp_ajax_url,
902			data: { action:'get_wbtm_bus_list', start_route:search.end_route, end_route:search.start_route, j_date:search.r_date, r_date:'', post_id:search.post_id },
903			beforeSend:function(){ dLoader(area); },
904			success:function(data){
905				area.addClass('etc_round_trip_active etc_round_trip_return_stage');
906				area.find('.wbtm_search_result').html('<div class="etc_round_trip_return_heading"><span>Step 2</span><strong>Select your return trip</strong><em>' + etcEscape(search.end_route) + ' → ' + etcEscape(search.start_route) + '</em></div>' + data).promise().done(function(){
907					dLoaderRemove(area); loadBgImage(); etcRenderPanel(area);
908				});
909			},
910			error:function(){ dLoaderRemove(area); alert('Could not load return trip results. Please try again.'); }
911		});
912	}
913
914	$(document).on('click', '#wbtm_area button.get_wbtm_bus_list', function () { etcResetRoundTrip(etcGetArea(this)); });
915	$(document).on('click change input', '.wbtm_registration_area', function () { etcUpdateSubmitText($(this).find('form').first()); });
916	$(document).on('click change input', '.etc_round_trip_return_stage .wbtm_registration_area .seat_available, .etc_round_trip_return_stage .wbtm_registration_area [name="wbtm_seat_qty[]"], .etc_round_trip_return_stage .wbtm_registration_area .wbtm_seat_item_list li', function(){
917		etcScheduleReturnPassengerReuse(this);
918	});
919	$(document).on('change', '.etc_round_trip_return_stage .etc_return_passenger_select', function(){
920		var select = $(this);
921		var item = select.closest('.wbtm_attendee_item');
922		var area = etcGetArea(select);
923		var state = area.data('etcRoundTripState') || null;
924		var index = parseInt(select.val(), 10);
925		item.attr('data-etc-return-passenger-touched', '1');
926		if (state && state.outboundPassengers && state.outboundPassengers[index]) {
927			etcFillPassengerItem(item, state.outboundPassengers[index], false);
928			item.attr('data-etc-return-passenger-filled', '1');
929			select.closest('.etc_return_passenger_reuse').find('small').text('Using details from the outbound trip. You can still edit them.');
930		} else {
931			etcFillPassengerItem(item, null, true);
932			item.removeAttr('data-etc-return-passenger-filled');
933			select.closest('.etc_return_passenger_reuse').find('small').text('Enter different details for this return passenger.');
934		}
935	});
936	$(document).on('input change', '.etc_round_trip_return_stage .wbtm_attendee_item input, .etc_round_trip_return_stage .wbtm_attendee_item textarea, .etc_round_trip_return_stage .wbtm_attendee_item select', function(){
937		if (!$(this).hasClass('etc_return_passenger_select')) {
938			$(this).closest('.wbtm_attendee_item').attr('data-etc-return-passenger-touched', '1');
939		}
940	});
941	$(document).on('click', '.etc_round_trip_edit_outbound', function () {
942		var area = etcGetArea(this), state = area.data('etcRoundTripState') || null;
943		if (!state || !state.search) { return; }
944		var search = state.search; etcResetRoundTrip(area);
945		$.ajax({ type:'POST', url:mp_ajax_url, data:{ action:'get_wbtm_bus_list', start_route:search.start_route, end_route:search.end_route, j_date:search.j_date, r_date:search.r_date, post_id:search.post_id }, beforeSend:function(){ dLoader(area); }, success:function(data){ area.find('.wbtm_search_result').html(data).promise().done(function(){ dLoaderRemove(area); loadBgImage(); }); }, error:function(){ dLoaderRemove(area); } });
946	});
947	$(document).on('click', '.wbtm_form_submit_area button[name="add-to-cart"]', function (e) {
948		if (e.isDefaultPrevented && e.isDefaultPrevented()) { return; }
949		var button = $(this), form = button.closest('form');
950		if (!form.length) { return; }
951		var area = etcGetArea(form), search = etcGetSearchValues(area);
952		if (!search.r_date) { return; }
953		if (typeof form[0].checkValidity === 'function' && !form[0].checkValidity()) { return; }
954		if (!area.hasClass('etc_round_trip_return_stage')) {
955			e.preventDefault();
956			var outboundSerialized = etcSerializeFormWithButton(form, button);
957			var state = { search:search, outboundSerialized:outboundSerialized, outboundSummary:etcLegSummaryFromForm(form), outboundPassengers:etcPassengersFromSerialized(outboundSerialized) };
958			area.data('etcRoundTripState', state).addClass('etc_round_trip_active');
959			etcRenderPanel(area); etcLoadReturnResults(area); return false;
960		}
961		var state = area.data('etcRoundTripState') || null;
962		if (!state || !state.outboundSerialized) { return; }
963		e.preventDefault();
964		state.returnSummary = etcLegSummaryFromForm(form); area.data('etcRoundTripState', state); etcRenderPanel(area);
965		var groupId = 'rt_' + (new Date().getTime()) + '_' + Math.random().toString(36).slice(2, 10);
966		var combinedTotal = (parseFloat(state.outboundSummary.total) || 0) + (parseFloat(state.returnSummary.total) || 0);
967		button.prop('disabled', true).addClass('etc_processing').text('Preparing checkout...');
968		$.ajax({ type:'POST', url:mp_ajax_url, dataType:'json', data:{ action:'etc_add_round_trip_leg', form_data:state.outboundSerialized, group_id:groupId, leg:'outbound', paired_leg_reference:'return', round_trip_total:combinedTotal.toFixed(2) }, success:function(response){
969			if (!response || !response.success) { button.prop('disabled', false).removeClass('etc_processing'); etcUpdateSubmitText(form); alert((response && response.data && response.data.message) ? response.data.message : 'Could not add outbound trip. Please try again.'); return; }
970			etcEnsureHidden(form, 'etc_round_trip_group_id', groupId); etcEnsureHidden(form, 'etc_trip_leg', 'return'); etcEnsureHidden(form, 'etc_paired_leg_reference', 'outbound'); etcEnsureHidden(form, 'etc_round_trip_requested', '1'); etcEnsureHidden(form, 'etc_round_trip_total', combinedTotal.toFixed(2)); etcEnsureHidden(form, 'add-to-cart', button.val() || form.find('input[name="etc_wc_product_id"]').val() || '');
971			if (form[0] && typeof form[0].submit === 'function') { form[0].submit(); }
972		}, error:function(){ button.prop('disabled', false).removeClass('etc_processing'); etcUpdateSubmitText(form); alert('Could not prepare the round trip checkout. Please try again.'); } });
973		return false;
974	});
975}(jQuery));
976
977/* Extracity v9.0 — Tab switching: One-Way / Return / Ticket Lookup */
978(function ($) {
979    'use strict';
980
981    /* ── activate a tab by key ── */
982    function etcActivateTab(area, key) {
983        area.find('.etc_tab_item').each(function () {
984            var isActive = $(this).data('tab') === key;
985            $(this).toggleClass('etc_tab_active', isActive);
986        });
987        area.find('.etc_tab_panel').each(function () {
988            var isActive = $(this).data('tab') === key;
989            $(this).toggleClass('etc_panel_active', isActive);
990        });
991        /* persist across soft reloads */
992        try { sessionStorage.setItem('etc_active_tab', key); } catch (e) {}
993    }
994
995    /* ── tab click — lock scroll position so panel height change does not jump the page ── */
996    $(document).on('click', '#wbtm_area .etc_tab_item', function () {
997        var area     = $(this).closest('#wbtm_area');
998        var scrollY  = window.pageYOffset || document.documentElement.scrollTop;
999        var areaTop  = area.offset().top;
1000
1001        etcActivateTab(area, $(this).data('tab'));
1002
1003        /* If the area top is above the current scroll position the page may
1004           reflow and shift — restore the exact scroll position immediately. */
1005        window.scrollTo({ top: scrollY, behavior: 'instant' });
1006
1007        /* Belt-and-suspenders: re-apply after any deferred reflows */
1008        requestAnimationFrame(function () {
1009            window.scrollTo({ top: scrollY, behavior: 'instant' });
1010        });
1011    });
1012
1013    /* ── restore last tab on page load ── */
1014    $(document).ready(function () {
1015        $('#wbtm_area').each(function () {
1016            var area = $(this);
1017            var saved = '';
1018            try { saved = sessionStorage.getItem('etc_active_tab') || ''; } catch (e) {}
1019            var validTabs = ['oneway', 'return', 'lookup'];
1020            var initial = validTabs.indexOf(saved) !== -1 ? saved : 'oneway';
1021            etcActivateTab(area, initial);
1022        });
1023    });
1024
1025}(jQuery));
1026
1027/* Extracity v9.17 — Return date validation and picker continuation */
1028(function ($) {
1029    'use strict';
1030
1031    function openReturnPicker(input) {
1032        if (!input.length) {
1033            return;
1034        }
1035        input.trigger('focus');
1036        if ($.fn.datepicker && input.hasClass('hasDatepicker')) {
1037            input.datepicker('show');
1038        }
1039    }
1040
1041    function clearReturnDateError(panel) {
1042        var visible = panel.find('.wbtm_return_date .wbtm_return_date_input').first();
1043        panel.find('.etc_return_date_error').removeClass('etc_visible');
1044        visible.removeClass('etc_return_date_missing').attr('aria-invalid', 'false');
1045    }
1046
1047    $(document).on('change input', '#etc_panel_return .wbtm_return_date input[name="r_date"], #etc_panel_return .wbtm_return_date_input', function () {
1048        clearReturnDateError($(this).closest('#etc_panel_return'));
1049    });
1050
1051    $(document).on('click', '#etc_panel_return .get_wbtm_bus_list', function (e) {
1052        var panel    = $(this).closest('#etc_panel_return');
1053        var field    = panel.find('.wbtm_return_date').first();
1054        var rHidden  = field.find('input[name="r_date"]').first();
1055        var rVisible = field.find('.wbtm_return_date_input').first();
1056        var errMsg   = field.find('.etc_return_date_error').first();
1057
1058        /* Backward-safe fallback in case cached markup does not yet contain
1059           the server-rendered validation element. */
1060        if (!errMsg.length) {
1061            field.append(
1062                '<span class="etc_return_date_error" role="alert" aria-live="polite">Please select a return date.</span>'
1063            );
1064            errMsg = field.find('.etc_return_date_error').first();
1065        }
1066
1067        var val = rHidden.val() ? rHidden.val().trim() : '';
1068
1069        if (!val) {
1070            e.stopImmediatePropagation();
1071            rVisible.addClass('etc_return_date_missing').attr('aria-invalid', 'true');
1072            errMsg.addClass('etc_visible');
1073            openReturnPicker(rVisible);
1074            return false;
1075        }
1076
1077        clearReturnDateError(panel);
1078    });
1079
1080}(jQuery));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.