PageSourceSearch

https://conningbrookashford.com/wp-content/themes/redcat/template-…/video-booking-form/script.js?ver=1.0.35

js conningbrookashford.com collected 2026-10-08 17:53:32 UTC 13,512 bytes, 446 lines download raw bytes

1/**
2 * Video Booking Form JavaScript
3 * Handles form submission, redirects to Guestline booking system, and placeholder image transitions
4 */
5
6document.addEventListener("DOMContentLoaded", function () {
7  // Handle play button functionality
8  handleVideoPlayButton();
9
10  // Handle placeholder image to video transition
11  handleVideoPlaceholderTransition();
12
13  // Handle booking form functionality
14  const form = document.getElementById("booking-form");
15
16  if (!form) {
17    console.warn("Video Booking Form: Form element not found");
18    return;
19  }
20
21  // Initialize Flatpickr for check-in and check-out
22  const checkinInput = document.getElementById("checkin");
23  const checkoutInput = document.getElementById("checkout");
24
25  if (checkinInput && checkoutInput && typeof flatpickr === "function") {
26
27    // Define checkout picker first (variable declaration)
28    let checkoutPicker;
29
30    // Initialize Check-in Picker
31    const checkinPicker = flatpickr(checkinInput, {
32      altInput: true,
33      altFormat: "d/m/Y",
34      dateFormat: "Y-m-d",
35      minDate: "today",
36      disableMobile: true,
37      onChange: function (selectedDates, dateStr) {
38        // Validation: Ensure checkout picker is ready
39        if (!checkoutPicker) return;
40
41        // Set the minimum date for checkout to the day after checkin
42        if (selectedDates.length > 0) {
43          const nextDay = new Date(selectedDates[0]);
44          nextDay.setDate(nextDay.getDate() + 1);
45          checkoutPicker.set("minDate", nextDay);
46
47          // Auto-select next day for check-out
48          // Logic: If no date selected OR selected date is invalid regarding new check-in
49          if (checkoutPicker.selectedDates.length === 0 || checkoutPicker.selectedDates[0] <= selectedDates[0]) {
50            checkoutPicker.setDate(nextDay);
51          }
52        }
53      }
54    });
55
56    // Initialize Check-out Picker
57    checkoutPicker = flatpickr(checkoutInput, {
58      altInput: true,
59      altFormat: "d/m/Y",
60      dateFormat: "Y-m-d",
61      minDate: "today",
62      disableMobile: true,
63      onOpen: function (selectedDates, dateStr, instance) {
64        // If checkin is selected but checkout minDate isn't set yet
65        if (checkinPicker && checkinPicker.selectedDates.length > 0) {
66          const nextDay = new Date(checkinPicker.selectedDates[0]);
67          nextDay.setDate(nextDay.getDate() + 1);
68          instance.set("minDate", nextDay);
69        }
70      }
71    });
72  }
73
74  // Handle form submission
75  form.addEventListener("submit", function (e) {
76    e.preventDefault();
77
78    // Get form data
79    const submitButton = form.querySelector('button[type="submit"]');
80    const blockContainer = document.querySelector(".redcat-video-booking-form");
81
82    // Get dynamic values from data attributes (like booking-buttons block)
83    const hotel =
84      submitButton?.dataset.hotel || blockContainer?.dataset.hotelCode;
85    const bookingBaseUrl =
86      blockContainer?.dataset.bookingUrl ||
87      "https://booking.eu.guestline.app/RCI/availability";
88
89    const checkin = document.getElementById("checkin")?.value;
90    const checkout = document.getElementById("checkout")?.value;
91    const guests = document.getElementById("guests")?.value;
92
93    // Validate all required fields
94    if (!hotel) {
95      alert(
96        "Hotel code is missing. Please configure the hotel code in block settings."
97      );
98      return;
99    }
100
101    if (!checkin) {
102      alert("Please select a check-in date.");
103      document.getElementById("checkin")?.focus();
104      return;
105    }
106
107    if (!checkout) {
108      alert("Please select a check-out date.");
109      document.getElementById("checkout")?.focus();
110      return;
111    }
112
113    if (!guests) {
114      alert("Please select the number of guests.");
115      document.getElementById("guests")?.focus();
116      return;
117    }
118
119    // Validate date logic
120    const checkinDate = new Date(checkin);
121    const checkoutDate = new Date(checkout);
122
123    if (checkoutDate <= checkinDate) {
124      alert("Check-out date must be after check-in date.");
125      document.getElementById("checkout")?.focus();
126      return;
127    }
128
129    // Show loading state
130    const originalText = submitButton.innerHTML;
131    submitButton.innerHTML = "<span>Loading...</span>";
132    submitButton.disabled = true;
133
134    try {
135      // Construct Guestline booking URL
136      const bookingParams = new URLSearchParams({
137        hotel: hotel,
138        arrival: checkin,
139        departure: checkout,
140        adults: guests,
141        children: "0",
142      });
143
144      const bookingUrl = `${bookingBaseUrl}?${bookingParams.toString()}`;
145
146      // Open booking page in new tab
147      window.open(bookingUrl, "_blank", "noopener,noreferrer");
148
149      // Reset form after successful submission
150      setTimeout(() => {
151        form.reset();
152        submitButton.innerHTML = originalText;
153        submitButton.disabled = false;
154      }, 1000);
155    } catch (error) {
156      console.error("Booking form error:", error);
157      alert(
158        error.message ||
159        "An error occurred while opening the booking page. Please try again."
160      );
161
162      // Restore button state
163      submitButton.innerHTML = originalText;
164      submitButton.disabled = false;
165    }
166  });
167
168  // Add loading states for better UX
169  const inputs = form.querySelectorAll("input, select");
170  inputs.forEach((input) => {
171    input.addEventListener("focus", function () {
172      this.parentElement.classList.add("focused");
173    });
174
175    input.addEventListener("blur", function () {
176      this.parentElement.classList.remove("focused");
177    });
178  });
179});
180
181/**
182 * Handles the transition from placeholder image to video when video loads
183 */
184function handleVideoPlaceholderTransition() {
185  const placeholderImage = document.querySelector(".video-placeholder-image");
186  const videoIframe = document.querySelector(".background-video-iframe");
187
188  if (!placeholderImage || !videoIframe) {
189    return; // No placeholder image or video, nothing to transition
190  }
191
192  // Function to fade out placeholder and fade in video
193  function transitionToVideo() {
194    // Add fade-out class to placeholder
195    placeholderImage.classList.add("fade-out");
196
197    // Add fade-in class to video and make it visible
198    videoIframe.classList.add("fade-in");
199
200    // Remove placeholder from DOM after transition completes
201    setTimeout(() => {
202      if (placeholderImage.classList.contains("fade-out")) {
203        placeholderImage.style.display = "none";
204      }
205    }, 800); // Match CSS transition duration
206  }
207
208  // Method 1: Listen for iframe load event
209  videoIframe.addEventListener("load", function () {
210    // Add a small delay to ensure video content starts playing
211    setTimeout(transitionToVideo, 500);
212  });
213
214  // Method 2: Fallback timer - transition after reasonable time even if load event doesn't fire
215  setTimeout(() => {
216    if (!placeholderImage.classList.contains("fade-out")) {
217      console.log("Video placeholder: Transitioning via fallback timer");
218      transitionToVideo();
219    }
220  }, 3000); // 3 seconds fallback
221
222  // Method 3: Enhanced detection for YouTube/Vimeo specific events
223  if (videoIframe.src.includes("youtube.com")) {
224    handleYouTubeTransition(videoIframe, transitionToVideo);
225  } else if (videoIframe.src.includes("vimeo.com")) {
226    handleVimeoTransition(videoIframe, transitionToVideo);
227  } else if (videoIframe.src.includes("cloudflarestream.com")) {
228    handleCloudflareStreamTransition(videoIframe, transitionToVideo);
229  }
230}
231
232/**
233 * Enhanced Cloudflare Stream video load detection
234 */
235function handleCloudflareStreamTransition(iframe, callback) {
236  // Cloudflare Stream has fast loading, similar approach to Vimeo
237  const checkCloudflareReady = () => {
238    try {
239      if (iframe.contentDocument !== null) {
240        callback();
241        return true;
242      }
243    } catch (e) {
244      // Expected CORS error
245    }
246    return false;
247  };
248
249  let attempts = 0;
250  const maxAttempts = 20;
251
252  const intervalId = setInterval(() => {
253    attempts++;
254    if (checkCloudflareReady() || attempts >= maxAttempts) {
255      clearInterval(intervalId);
256      if (attempts >= maxAttempts) {
257        console.log(
258          "Cloudflare Stream transition: Max attempts reached, proceeding with transition"
259        );
260        callback();
261      }
262    }
263  }, 250);
264}
265
266/**
267 * Enhanced YouTube video load detection
268 */
269function handleYouTubeTransition(iframe, callback) {
270  // YouTube iframe API detection
271  const checkYouTubeReady = () => {
272    try {
273      // This will fail due to CORS, but iframe.contentDocument being accessible
274      // often indicates the iframe content has loaded
275      if (iframe.contentDocument !== null) {
276        callback();
277        return true;
278      }
279    } catch (e) {
280      // Expected CORS error, but we can still use other indicators
281    }
282    return false;
283  };
284
285  // Check every 250ms for up to 5 seconds
286  let attempts = 0;
287  const maxAttempts = 20;
288
289  const intervalId = setInterval(() => {
290    attempts++;
291    if (checkYouTubeReady() || attempts >= maxAttempts) {
292      clearInterval(intervalId);
293      if (attempts >= maxAttempts) {
294        console.log(
295          "YouTube transition: Max attempts reached, proceeding with transition"
296        );
297        callback();
298      }
299    }
300  }, 250);
301}
302
303/**
304 * Enhanced Vimeo video load detection
305 */
306function handleVimeoTransition(iframe, callback) {
307  // Similar approach for Vimeo
308  const checkVimeoReady = () => {
309    try {
310      if (iframe.contentDocument !== null) {
311        callback();
312        return true;
313      }
314    } catch (e) {
315      // Expected CORS error
316    }
317    return false;
318  };
319
320  let attempts = 0;
321  const maxAttempts = 20;
322
323  const intervalId = setInterval(() => {
324    attempts++;
325    if (checkVimeoReady() || attempts >= maxAttempts) {
326      clearInterval(intervalId);
327      if (attempts >= maxAttempts) {
328        console.log(
329          "Vimeo transition: Max attempts reached, proceeding with transition"
330        );
331        callback();
332      }
333    }
334  }, 250);
335}
336
337/**
338 * Handles play button click to start video playback
339 */
340function handleVideoPlayButton() {
341  const playButtonOverlay = document.querySelector(".video-play-button-overlay");
342  const playButton = document.querySelector(".video-play-button");
343  const videoIframe = document.querySelector(".background-video-iframe");
344
345  if (!playButtonOverlay || !playButton || !videoIframe) {
346    return; // No play button or video, nothing to handle
347  }
348
349  // Get autoplay status from iframe data attribute
350  const autoplay = videoIframe.getAttribute("data-autoplay") === "true";
351
352  // If autoplay is enabled, don't show play button
353  if (autoplay) {
354    return;
355  }
356
357  // Handle play button click
358  playButton.addEventListener("click", function () {
359    const src = videoIframe.src;
360    const shouldLoop = videoIframe.getAttribute("data-loop") === "true";
361
362    // Hide the play button overlay
363    playButtonOverlay.classList.add("hidden");
364
365    // Recreate the iframe URL with autoplay enabled
366    let newSrc = src;
367
368    if (src.includes("youtube.com")) {
369      // YouTube: add/update autoplay and mute parameters
370      newSrc = updateUrlParameter(src, "autoplay", "1");
371      newSrc = updateUrlParameter(newSrc, "mute", "1");
372
373      // Add loop parameter if enabled
374      if (shouldLoop) {
375        newSrc = updateUrlParameter(newSrc, "loop", "1");
376        // YouTube requires playlist parameter for loop to work
377        const videoId = extractYouTubeVideoId(src);
378        if (videoId) {
379          newSrc = updateUrlParameter(newSrc, "playlist", videoId);
380        }
381      }
382    } else if (src.includes("vimeo.com")) {
383      // Vimeo: add/update autoplay and muted parameters
384      newSrc = updateUrlParameter(src, "autoplay", "1");
385      newSrc = updateUrlParameter(newSrc, "muted", "1");
386
387      // Add loop parameter if enabled
388      if (shouldLoop) {
389        newSrc = updateUrlParameter(newSrc, "loop", "1");
390      }
391    } else if (src.includes("cloudflarestream.com")) {
392      // Cloudflare Stream: add/update autoplay and muted parameters
393      newSrc = updateUrlParameter(src, "autoplay", "true");
394      newSrc = updateUrlParameter(newSrc, "muted", "true");
395
396      // Add loop parameter if enabled
397      if (shouldLoop) {
398        newSrc = updateUrlParameter(newSrc, "loop", "true");
399      }
400    }
401
402    // Update iframe src to trigger video playback
403    videoIframe.src = newSrc;
404
405    // Make video visible if it was hidden
406    videoIframe.style.opacity = "1";
407    videoIframe.classList.add("fade-in");
408  });
409}
410
411/**
412 * Helper function to update URL parameters
413 * @param {string} url - The URL to update
414 * @param {string} param - The parameter name
415 * @param {string} value - The parameter value
416 * @returns {string} Updated URL
417 */
418function updateUrlParameter(url, param, value) {
419  const urlObj = new URL(url);
420  urlObj.searchParams.set(param, value);
421  return urlObj.toString();
422}
423
424/**
425 * Extract YouTube video ID from embed URL
426 * @param {string} url - The YouTube embed URL
427 * @returns {string|null} Video ID or null
428 */
429function extractYouTubeVideoId(url) {
430  try {
431    const urlObj = new URL(url);
432    // Check if playlist parameter already exists
433    const existingPlaylist = urlObj.searchParams.get("playlist");
434    if (existingPlaylist) {
435      return existingPlaylist;
436    }
437    // Extract from /embed/VIDEO_ID format
438    const pathMatch = urlObj.pathname.match(/\/embed\/([^/?]+)/);
439    if (pathMatch && pathMatch[1]) {
440      return pathMatch[1];
441    }
442  } catch (e) {
443    console.error("Error extracting YouTube video ID:", e);
444  }
445  return null;
446}

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.