1/** 2 * Architect Modal Forms â Frontend Modal JS 3 * Handles: triggers, AJAX loading, open/close, CF7 init, thank you, tracking 4 */ 5(function () { 6 'use strict'; 7 8 // Cache loaded modals 9 var cache = {}; 10 var activeModal = null; 11 var scrollbarWidth = 0; 12 13 // âââ Init ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 14 document.addEventListener('DOMContentLoaded', function () { 15 scrollbarWidth = window.innerWidth - document.documentElement.clientWidth; 16 document.documentElement.style.setProperty('--amf-scrollbar-width', scrollbarWidth + 'px'); 17 18 bindTriggers(); 19 bindHashTrigger(); 20 bindCloseButtons(); 21 bindCF7Submit(); 22 }); 23 24 // âââ Triggers ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 25 26 function bindTriggers() { 27 // data-amf-open="ID" 28 document.addEventListener('click', function (e) { 29 var trigger = e.target.closest('[data-amf-open]'); 30 if (!trigger) return; 31 32 e.preventDefault(); 33 var id = parseInt(trigger.getAttribute('data-amf-open'), 10); 34 if (id > 0) openModal(id); 35 }); 36 37 // href="#amf-ID" 38 document.addEventListener('click', function (e) { 39 var link = e.target.closest('a[href*="#amf-"]'); 40 if (!link) return; 41 42 var href = link.getAttribute('href'); 43 var match = href.match(/#amf-(\d+)/); 44 if (match) { 45 e.preventDefault(); 46 openModal(parseInt(match[1], 10)); 47 } 48 }); 49 } 50 51 function bindHashTrigger() { 52 // Open modal from URL hash on page load 53 var hash = window.location.hash; 54 if (hash) { 55 var match = hash.match(/#amf-(\d+)/); 56 if (match) { 57 openModal(parseInt(match[1], 10)); 58 } 59 } 60 } 61 62 // âââ Open Modal ââââââââââââââââââââââââââââââââââââââââââââââââââââââ 63 64 function openModal(id) { 65 // Check if already in DOM (popup or shortcode rendered it) 66 var existing = document.getElementById('amf-modal-' + id); 67 68 if (existing) { 69 showModal(existing); 70 return; 71 } 72 73 // Check cache 74 if (cache[id]) { 75 injectAndShow(id, cache[id]); 76 return; 77 } 78 79 // AJAX load 80 var formData = new FormData(); 81 formData.append('action', 'amf_get_modal'); 82 formData.append('id', id); 83 84 fetch(AMF_DATA.ajaxUrl, { 85 method: 'POST', 86 body: formData, 87 credentials: 'same-origin' 88 }) 89 .then(function (r) { return r.json(); }) 90 .then(function (resp) { 91 if (resp.success && resp.data.html) { 92 cache[id] = resp.data.html; 93 injectAndShow(id, resp.data.html); 94 } 95 }) 96 .catch(function (err) { 97 console.error('AMF: Failed to load modal', err); 98 }); 99 } 100 101 function injectAndShow(id, html) { 102 var root = document.getElementById('amf-root'); 103 if (!root) { 104 // Fallback: create #amf-root if missing 105 root = document.createElement('div'); 106 root.id = 'amf-root'; 107 document.body.appendChild(root); 108 } 109 var temp = document.createElement('div'); 110 temp.innerHTML = html; 111 var overlay = temp.firstElementChild; 112 root.appendChild(overlay); 113 showModal(overlay); 114 } 115 116 function showModal(overlay) { 117 if (!overlay) return; 118 119 overlay.style.display = ''; 120 overlay.setAttribute('aria-hidden', 'false'); 121 122 // Force reflow 123 overlay.offsetHeight; 124 125 // Show 126 overlay.classList.add('is-visible'); 127 overlay.classList.remove('is-closing'); 128 activeModal = overlay; 129 130 // Disable scroll 131 if (overlay.getAttribute('data-disable-scroll') === '1') { 132 document.body.classList.add('amf-no-scroll'); 133 } 134 135 // Init CF7 inside 136 initCF7(overlay); 137 138 // Track open 139 var modalId = overlay.getAttribute('data-modal-id'); 140 if (modalId) { 141 trackEvent(modalId, 'open'); 142 } 143 144 // Close on overlay click 145 if (overlay.getAttribute('data-close-overlay') === '1') { 146 overlay.addEventListener('click', onOverlayClick); 147 } 148 149 // Close on ESC 150 if (overlay.getAttribute('data-close-esc') === '1') { 151 document.addEventListener('keydown', onEscKey); 152 } 153 } 154
155 // âââ Close Modal âââââââââââââââââââââââââââââââââââââââââââââââââââââ 156 157 function bindCloseButtons() { 158 document.addEventListener('click', function (e) { 159 if (e.target.closest('.amf-close')) { 160 closeActiveModal(); 161 } 162 }); 163 } 164 165 function closeActiveModal() { 166 if (!activeModal) return; 167 168 var overlay = activeModal; 169 overlay.classList.add('is-closing'); 170 overlay.classList.remove('is-visible'); 171 172 // Remove listeners 173 overlay.removeEventListener('click', onOverlayClick); 174 document.removeEventListener('keydown', onEscKey); 175 176 // After animation 177 setTimeout(function () { 178 overlay.classList.remove('is-closing'); 179 // ÐÐÐÐÐ: display надо гаÑиÑÑ Ñ !important. Ð modal.css Ñ .amf-overlay 180 // ÑÑÐ¾Ð¸Ñ `display:flex !important`, поÑÑÐ¾Ð¼Ñ Ð¾Ð±ÑÑнÑй inline display:none 181 // пÑоигÑÑÐ²Ð°ÐµÑ Ð¸ овеÑлей оÑÑаÑÑÑÑ Ð² DOM â невидимÑй (opacity:0), но 182 // кликабелÑнÑй (Ð¿Ð¾Ð»Ñ ÑоÑÑиÑÑÑÑ pointer-events/visibility). ÐÑÑÑда бÑла 183 // «ÑоÑма-пÑизÑак». setProperty Ñ 'important' пеÑÐµÐ±Ð¸Ð²Ð°ÐµÑ Ð¿Ñавило. 184 overlay.style.setProperty('display', 'none', 'important'); 185 overlay.setAttribute('aria-hidden', 'true'); 186 document.body.classList.remove('amf-no-scroll'); 187 activeModal = null; 188 }, 300); 189 } 190 191 function onOverlayClick(e) { 192 // Only if clicked directly on overlay (not modal content) 193 if (e.target === e.currentTarget) { 194 closeActiveModal(); 195 } 196 } 197 198 function onEscKey(e) { 199 if (e.key === 'Escape') { 200 closeActiveModal(); 201 } 202 } 203 204 // âââ Open modal externally (for popup.js) ââââââââââââââââââââââââââââ 205 window.AMF_openModal = openModal; 206 window.AMF_closeActiveModal = closeActiveModal; 207 208 // âââ CF7 Init ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 209 210 function initCF7(overlay) { 211 if (typeof wpcf7 === 'undefined') return; 212 213 var forms = overlay.querySelectorAll('.wpcf7 > form'); 214 forms.forEach(function (form) { 215 // Don't init twice 216 if (form.classList.contains('amf-cf7-inited')) return; 217 form.classList.add('amf-cf7-inited'); 218 219 if (typeof wpcf7.init === 'function') { 220 wpcf7.init(form); 221 } 222 }); 223 224 // Re-init phone mask (cf7-phone-mask-field / Inputmask) 225 initPhoneMasks(overlay); 226 } 227 228 /** 229 * Re-initialize phone masks inside modal 230 * Supports: cf7-phone-mask-field (Inputmask), jQuery Mask Plugin 231 */ 232 function initPhoneMasks(overlay) { 233 // Wait a tick for CF7 to finish rendering 234 setTimeout(function () { 235 var inputs = overlay.querySelectorAll('input[data-inputmask], input[data-mask], input.masked, input.phone-mask, input[type="tel"]'); 236 237 inputs.forEach(function (input) { 238 // Inputmask (cf7-phone-mask-field uses this) 239 if (typeof Inputmask !== 'undefined') { 240 var maskData = input.getAttribute('data-inputmask'); 241 if (maskData) { 242 try { 243 var opts = {}; 244 // Parse data-inputmask="'mask': '+7(999)999-99-99'" format 245 maskData.replace(/'([^']+)'\s*:\s*'([^']+)'/g, function (m, k, v) { 246 opts[k] = v; 247 }); 248 if (opts.mask) { 249 Inputmask(opts).mask(input); 250 } 251 } catch (e) { 252 console.warn('AMF: Inputmask init error', e); 253 } 254 } 255 } 256 257 // jQuery Mask Plugin fallback 258 if (typeof jQuery !== 'undefined' && typeof jQuery.fn.mask === 'function') { 259 var jqMask = input.getAttribute('data-mask'); 260 if (jqMask) { 261 jQuery(input).mask(jqMask); 262 } 263 } 264 }); 265 266 // Trigger custom event for other mask plugins 267 var event = new CustomEvent('amf:modal:opened', { bubbles: true }); 268 overlay.dispatchEvent(event); 269 }, 100); 270 } 271 272 // âââ CF7 Form Submit â Thank You âââââââââââââââââââââââââââââââââââââ 273 274 function bindCF7Submit() { 275 document.addEventListener('wpcf7mailsent', function (e) { 276 var form = e.target;
277 var overlay = form.closest('.amf-overlay'); 278 if (!overlay) return; 279 280 var modalId = overlay.getAttribute('data-modal-id'); 281 282 // Track submit 283 if (modalId) { 284 trackEvent(modalId, 'submit'); 285 } 286 287 // DataLayer 288 var dlEvent = overlay.getAttribute('data-datalayer'); 289 if (dlEvent) { 290 window.dataLayer = window.dataLayer || []; 291 window.dataLayer.push({ 292 event: dlEvent, 293 amf_modal_id: modalId 294 }); 295 } 296 297 // Conversion script 298 var scriptEl = overlay.querySelector('.amf-conversion-script'); 299 if (scriptEl) { 300 try { 301 new Function(scriptEl.textContent)(); 302 } catch (err) { 303 console.error('AMF conversion script error:', err); 304 } 305 } 306 307 // Show Thank You 308 if (overlay.getAttribute('data-show-thankyou') === '1') { 309 closeActiveModal(); 310 setTimeout(function () { 311 showThankYou(); 312 }, 350); 313 } 314 }); 315 } 316 317 // âââ Thank You Modal âââââââââââââââââââââââââââââââââââââââââââââââââ 318 319 function showThankYou() { 320 var ty = document.getElementById('amf-thankyou-overlay'); 321 if (!ty) return; 322 323 var config = AMF_DATA.thankyou || {}; 324 325 // Populate content 326 var logo = document.getElementById('amf-ty-logo'); 327 if (logo) { 328 logo.src = config.logoUrl || ''; 329 logo.style.display = config.logoUrl ? '' : 'none'; 330 } 331 332 var heading = document.getElementById('amf-ty-heading'); 333 if (heading) heading.textContent = config.heading || 'СпаÑибо!'; 334 335 var sub = document.getElementById('amf-ty-subheading'); 336 if (sub) sub.textContent = config.subheading || ''; 337 338 var desc = document.getElementById('amf-ty-description'); 339 if (desc) desc.textContent = config.description || ''; 340 341 // Show 342 ty.style.display = ''; 343 ty.offsetHeight; // reflow 344 ty.classList.add('is-visible'); 345 346 // Close button 347 var closeBtn = ty.querySelector('.amf-thankyou-close'); 348 if (closeBtn) { 349 closeBtn.onclick = function () { hideThankYou(); }; 350 } 351 352 // Click outside 353 ty.addEventListener('click', function tyOverlayClick(e) { 354 if (e.target === ty) { 355 hideThankYou(); 356 ty.removeEventListener('click', tyOverlayClick); 357 } 358 }); 359 360 // Auto close 361 var autoClose = parseInt(config.autoClose, 10); 362 if (autoClose > 0) { 363 setTimeout(function () { 364 hideThankYou(); 365 }, autoClose * 1000); 366 } 367 } 368 369 function hideThankYou() { 370 var ty = document.getElementById('amf-thankyou-overlay'); 371 if (!ty) return; 372 373 ty.classList.remove('is-visible'); 374 setTimeout(function () { 375 ty.style.display = 'none'; 376 }, 300); 377 } 378 379 // âââ Analytics Tracking ââââââââââââââââââââââââââââââââââââââââââââââ 380 381 function trackEvent(modalId, event) { 382 var formData = new FormData(); 383 formData.append('action', 'amf_track'); 384 formData.append('modal_id', modalId); 385 formData.append('event', event); 386 387 fetch(AMF_DATA.ajaxUrl, { 388 method: 'POST', 389 body: formData, 390 credentials: 'same-origin' 391 }).catch(function () { /* silent */ }); 392 } 393 394})();
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.