/** * Check if page has been loaded inside an iframe. * * @returns {boolean} */ function isInIframe() { try { return window.self !== window.top; } catch (e) { return true; } } var ResponsiveIframe = (function () { var _iframeOffsetTop = 0; /** * Include other elements to calculate iframe height. * * @return {number} */ function getAdditionalHeight() { var height = 0; document.querySelectorAll(".dropdown-menu.show", ".smartbanner").forEach(el => { height += el.offsetHeight; }); return height; } function getDocHeight() { var h = document.body.offsetHeight + getAdditionalHeight(); return h; } /** * Update AM iframe size. * * @param {boolean} scrollTop True for scrolling to the top of the page. */ function messageParent(scrollTop) { if (typeof scrollTop != "boolean") { scrollTop = false; } var h = getDocHeight(); var message = { height: h, scrollTop: scrollTop, }; top.postMessage(message, "*"); } function sendParentMessage(type, data) { var message = {}; data = data || {}; switch (type) { case "CONFIG": message = { type: "CONFIG", manual_offset: Skubacz.configuration.manual_offset, offset_elements: Skubacz.configuration.offset_elements, fullscreen_modal_fix: Skubacz.configuration.fullscreen_modal_fix, }; break; case "SIZE_STRETCH": message = { type: "SIZE_STRETCH", applyLayoutFix: data.hasOwnProperty("applyLayoutFix") ? data.applyLayoutFix : false, fullscreenModalFix: data.hasOwnProperty("fullscreenModalFix") ? data.fullscreenModalFix : false, }; break; case "SIZE_DEFAULT": message = { type: "SIZE_DEFAULT", }; break; case "SCROLL_TO_IFRAME": message = { type: "SCROLL_TO_IFRAME", extraOffsetTop: data.hasOwnProperty("extraOffsetTop") ? parseInt(data.extraOffsetTop) : 0, }; break; case "HISTORY_PUSH": message = { type: "HISTORY_PUSH", path: data.path, }; break; } top.postMessage(message, "*"); } function setModalDialogOffset(iframeFixedOffsetTop) { document.querySelectorAll(".modal-dialog").forEach(el => { el.style.marginTop = iframeFixedOffsetTop ? iframeFixedOffsetTop + "px" : ""; }); } function onMessage(e) { switch (e.data.type) { case "PARENT_SCROLL": _iframeOffsetTop = e.data.iframeOffsetTop; break; case "INIT_DYNAMIC_IFRAME": document.body.classList.remove("l-am-regular-iframe"); document.body.classList.add("l-am-dynamic-iframe"); // add class for AM with dynamically adjusted iframe height document.dispatchEvent( new CustomEvent("dynamic-iframe.skubacz.active-menu", { detail: { contentWrapper: "#wrapper", activeMenu: { adjustSize: function () { ResponsiveIframe.messageParent(false); }, }, }, }) ); fixIframeModal(); break; case "HIDE_MODAL": // Bootstrap modal const openModal = document.querySelector(".modal.show"); if (openModal) { const openBSModal = window.Modal.getOrCreateInstance(".modal.show"); openBSModal.hide(); } // CC modal const isCcSettingsModalOpen = document.documentElement.classList.contains("show--preferences"); if (isCcSettingsModalOpen) { window.CC.hidePreferences(); } break; case "SET_FULLSCREEN_OFFSET": var iframeOffsetTop = parseInt(e.data.iframeOffsetTop); var iframeFixedOffsetTop = parseInt(e.data.iframeFixedOffsetTop); // Set offset to wrapper element in order to mask content displacement due iframe fixed position (fullscreen mode). document.getElementById("wrapper").style.top = iframeOffsetTop !== 0 ? iframeOffsetTop + "px" : ""; // If offset top is equal to 0 at this point then it means `#wrapper` doesn't need it (in this context "" means disabled). document.body.classList.remove("l-am-fullscreen-loading-fix"); setModalDialogOffset(iframeFixedOffsetTop); break; case "SET_FULLSCREEN_MODAL_OFFSET": setModalDialogOffset(parseInt(e.data.iframeFixedOffsetTop)); break; case "DISABLE_FULLSCREEN_OFFSET": document.getElementById("wrapper").style.top = ""; break; case "TRACK_EVENT": ahoy.track(e.data.name, e.data.params); break; case "INIT_ANALYTICS": Skubacz.tracking({ tag: "InitActiveMenu", clientId: e.data.clientId }); break; } } /** * Update iframe size on layout change (e.g adding or removing content, lazy-loading etc.). */ function updateOnSizeChange() { try { // Update when layout size changes. new ResizeObserver(function () { ResponsiveIframe.messageParent(false); }).observe(document.getElementsByTagName("main")[0]); } catch (e) { // Fallback: update iframe size on image lazy-loading. [].forEach.call( document.querySelectorAll('[loading="lazy"]'), function (img) { img.onload = function () { ResponsiveIframe.messageParent(false); }; } ); } } function onLoad() { messageParent(false); updateOnSizeChange(); } /** * Set of fixes for responsive fullscreen modals within iframe with dynamically adjusted iframe height. */ function fixIframeModal() { if (!isInIframe() || Skubacz.configuration.editing) { return; } var iframe = ResponsiveIframe; document.getElementById("wrapper").style.top = ""; // make sure `#wrapper` doesn't have any `top` value which is used to determine fullscreen offset fix presence /** * Remove "mask" class used to hide "jumping effect" which is added while showing a modal (iframe fullscreen mode). * * - fixes sequential modals when events order can be as follows: hide, show, hidden, shown (bug occurs with JS modal api in creator>cart communication) * - ensures removal of the "mask" class with iframe async communication */ function removeModalMaskOnHidden() { var counter = 0; var counterLimit = 50; // define the number of attempts (make sure function will not run forever - just in case) (function removeModalMask() { counter++; if ( document.getElementById("wrapper").style.top === "" || counter > counterLimit ) { document.body.classList.remove("l-am-fullscreen-loading-fix"); } else if (document.body.classList.contains("modal-open")) { // `.modal-open` presence means that another modal is open so "mask" class is not required setTimeout(function () { document.body.classList.remove("l-am-fullscreen-loading-fix"); }, 5); } else { setTimeout(function () { return removeModalMask(); }, 5); } })(); } document.addEventListener("show.bs.modal", function (e) { var modal = e.target; var iframeOffsetTop = iframe.getIframeOffsetTop(); var isDistanceFromTop; if (Skubacz.configuration.fullscreen_modal_fix === true) { isDistanceFromTop = iframeOffsetTop - window.pageYOffset > 0; if (isDistanceFromTop) { iframe.sendParentMessage("SCROLL_TO_IFRAME"); } } // If there is no offset top at this point then it means fullscreen loading fix is not needed. if (iframeOffsetTop !== 0) { document.body.classList.add("l-am-fullscreen-loading-fix"); } iframe.sendParentMessage("SIZE_STRETCH"); modal.classList.add("fade"); // enable smooth transition for modal entrance to avoid "jumping" effect }); document.addEventListener("shown.bs.modal", function (e) { var modal = e.target; var modalBackdrop = document.querySelector(".modal-backdrop"); // Remove fade out animation to match visibility with AM parent site (also required for sequential modals with custom backdrop). modal.classList.remove("fade"); if (modalBackdrop) { modalBackdrop.classList.remove("fade"); } iframe.sendParentMessage("SIZE_STRETCH", { applyLayoutFix: true, fullscreenModalFix: Skubacz.configuration.fullscreen_modal_fix, }); document.body.classList.remove("l-am-fullscreen-loading-fix"); }); document.addEventListener("hide.bs.modal", function (e) { // If `#wrapper` doesn't have offset enabled at this point then it means fullscreen loading fix is not needed. if (document.getElementById("wrapper").style.top !== "") { document.body.classList.add("l-am-fullscreen-loading-fix"); } iframe.sendParentMessage("SIZE_DEFAULT"); }); document.addEventListener("hidden.bs.modal", function (e) { removeModalMaskOnHidden(); }); window.addEventListener("cc:onModalShow", (e) => { if (e.detail.modalName === "preferencesModal") { iframe.sendParentMessage("SIZE_STRETCH"); } }); window.addEventListener("cc:onModalHide", (e) => { if (e.detail.modalName === "preferencesModal") { iframe.sendParentMessage("SIZE_DEFAULT"); } }); } function init() { window.addEventListener("load", onLoad, false); window.addEventListener("resize", messageParent, false); window.addEventListener("message", onMessage, false); } return { init: init, getIframeOffsetTop: function () { return _iframeOffsetTop; }, messageParent: messageParent, sendParentMessage: sendParentMessage, }; })(); // Enable Scroll.js functionality for AM (required adjustments for iframe). var scrollAM = (function () { var iframe = ResponsiveIframe; /** * Get the current coordinates of the element (relative to the document). * * @param {Element} el * @returns {object} */ function offset(el) { box = el.getBoundingClientRect(); docElem = document.documentElement; return { top: box.top + window.pageYOffset - docElem.clientTop, left: box.left + window.pageXOffset - docElem.clientLeft }; } /** * Determines whether the argument represents a JavaScript number. * * @param num * @returns {boolean} */ function isNumeric(num) { if (typeof num === 'number') return num - num === 0; if (typeof num === 'string' && num.trim() !== '') return Number.isFinite(+num); return false; } /** * Check for correct target element. * * @param target * @returns {boolean} */ function isCorrectTarget(target) { if (target instanceof Element) { return true; } else { console.warn("Provide correct DOM element!"); return false; } } /** * Scroll to specific position. * * @param scrollTop Position within iframe to scroll to along the y-axis in pixels. */ function to(scrollTop) { if (!isNumeric(scrollTop)) { throw new Error("scrollTop needs to be a number!"); } return iframe.sendParentMessage("SCROLL_TO_IFRAME", { extraOffsetTop: scrollTop, }); } /** * Scroll to specific element. * * @param target */ function toElement(target) { if (!isCorrectTarget(target)) { return; } return to(offset(target).top); } /** * Scroll to element if target is above our position. * * @param target */ function scrollIfAbove(target) { if (!isCorrectTarget(target)) { return; } var targetOffsetTop = offset(target).top; var targetDistance = iframe.getIframeOffsetTop() + targetOffsetTop; if (targetDistance < 0) { return to(targetOffsetTop); } } return { to: to, toElement: toElement, scrollIfAbove: scrollIfAbove, }; })(); document.addEventListener('DOMContentLoaded', () => { var iframe = ResponsiveIframe; const getPath = () => window.location.pathname + window.location.search + window.location.hash; iframe.init(); iframe.messageParent(false); iframe.sendParentMessage("CONFIG"); iframe.sendParentMessage("HISTORY_PUSH", { path: getPath() }); if (document.querySelector(".js-checkout")) { iframe.sendParentMessage("SCROLL_TO_IFRAME"); } if (document.querySelector(".js-payment")) { iframe.sendParentMessage("SCROLL_TO_IFRAME"); } if (document.querySelector(".js-order-result")) { iframe.sendParentMessage("SCROLL_TO_IFRAME"); } document.addEventListener("resize.skubacz.checkout", function () { iframe.messageParent(false); }); document.addEventListener("resize.skubacz.orderInfo", function () { iframe.messageParent(false); }); document.querySelectorAll("[data-resize-on]").forEach(function (el) { var e = el.getAttribute("data-resize-on"); el.addEventListener(e, function () { setTimeout(function () { iframe.messageParent(false); }, 200); }); }); document.querySelectorAll("[data-toggle]").forEach(function (el) { el.addEventListener("click", function () { setTimeout(function () { iframe.messageParent(false); }, 100); }); }); document.addEventListener("shown.restaumatic.group-toggle", function (event) { iframe.messageParent(false); if (event.detail.mode === "list-all") { scrollAM.toElement(event.detail.el); } else { scrollAM.scrollIfAbove(event.detail.el); } iframe.sendParentMessage("HISTORY_PUSH", { path: getPath() }); }); document.addEventListener("hidden.restaumatic.group-toggle", function (event) { // Prevent double resize by checking for active group. if (typeof event.detail.relatedTarget === "undefined") { // There is no active group, so there won't be `shown` event, therefore we can resize iframe here. iframe.messageParent(false); iframe.sendParentMessage("HISTORY_PUSH", { path: getPath() }); } }); if (!isInIframe()) { console.log("Running outside of iframe, initialize analytics"); Skubacz.tracking({ tag: "InitActiveMenu", clientId: null }); document.body.classList.add("l-am-standalone"); // add class for standalone version } else { if (!document.body.classList.contains("l-am-dynamic-iframe")) { // Add class name for regular iframe (it's different than `l-am-dynamic-iframe` which stands for iframe with dynamically adjusted iframe height and can be set by AM parent). document.body.classList.add("l-am-regular-iframe"); } // Fix img lazy loading issues in Safari: https://restaumatic.atlassian.net/browse/RS-5188 if (Skubacz.Device.browser.isSafari()) { document.querySelectorAll('img[loading="lazy"]').forEach(function (img) { img.setAttribute("loading", "eager"); }); } } if (isInIframe() && !document.body.classList.contains("l-am-regular-iframe")) { window.addEventListener("scroll", function () { // Prevent unwanted scroll of the window and displacement of the content within iframe. // // Bug is related to hash change and it occurs when hash with target element is added to the URL, // e.g. `link
content
` on click. if (window.pageYOffset || document.documentElement.scrollTop) { window.scrollTo(0, 0); } }); } });