PageSourceSearch

https://agroexpert.kz/wp-content/plugins/architect-modal-forms-v3/assets/js/modal.js?ver=3.0.9

js agroexpert.kz collected 2026-10-07 23:03:14 UTC 13,900 bytes, 394 lines download raw bytes

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.