PageSourceSearch

https://vnuivnu.ru/wa-files/js/checkout-goals.js?v=4

js vnuivnu.ru collected 2026-10-08 00:55:24 UTC 7,012 bytes, 149 lines download raw bytes

1/**
2 * Цели Яндекс.Метрики на шаги оформления заказа.
3 *
4 * Оформление на витрине — одностраничное (Webasyst checkout 2): секции
5 * `.wa-step-*-section` живут на одной странице, шагов-переходов нет. Поэтому
6 * «шаг» здесь — заполненное поле или выбранный вариант, а не смена экрана.
7 * Без этих целей в Метрике не собирается воронка и не видно, где отваливаются.
8 *
9 * Счётчик подключается плагином `delay` отложенно, поэтому цели складываются
10 * в очередь и уходят, как только появится `window.ym`.
11 *
12 * Цели, которые должны быть заведены в Метрике как «JavaScript-событие»:
13 *   CHECKOUT_OPEN      — открыл оформление с непустой корзиной
14 *   CHECKOUT_ADDRESS   — ввёл адрес
15 *   CHECKOUT_DATETIME  — выбрал дату или интервал доставки
16 *   CHECKOUT_CONTACTS  — ввёл телефон
17 *   CHECKOUT_SUBMIT    — нажал «Оформить заказ»
18 *   CHECKOUT_ERROR     — увидел ошибку в форме
19 *   CHECKOUT_ZONE_CHANGED — менял зону доставки (подставленная по умолчанию не подошла)
20 *   ORDER — отправляет плагин «E-commerce аналитика
20» при создании заказа
21 */
22(function () {
23    'use strict';
24
25    var COUNTER = 102670702;
26    var QUEUE_LIMIT_MS = 30000;
27
28    var pending = [];
29    var already_sent = {};
30
31    /** Отправляет цель один раз за загрузку страницы; до готовности счётчика копит очередь. */
32    function goal(name, params) {
33        if (already_sent[name]) { return; }
34        already_sent[name] = true;
35        pending.push([name, params]);
36        flush();
37    }
38
39    function flush() {
40        if (typeof window.ym !== 'function') { return; }
41        while (pending.length) {
42            var item = pending.shift();
43            window.ym(COUNTER, 'reachGoal', item[0], item[1] || undefined);
44        }
45    }
46
47    // Счётчик приезжает вторым запросом, поэтому очередь разбираем по таймеру.
48    var waiting_since = Date.now();
49    var waiting = setInterval(function () {
50        flush();
51        if (typeof window.ym === 'function' || Date.now() - waiting_since > QUEUE_LIMIT_MS) {
52            clearInterval(waiting);
53        }
54    }, 300);
55
56    function filled(selector) {
57        var node = document.querySelector(selector);
58        return !!(node && String(node.value || '').trim());
59    }
60
61    // Зона доставки подставлена по умолчанию у всех, поэтому интересен только её
62    // осознанный выбор — то есть отличие от того, что стояло при открытии формы.
63    var initial_zone = null;
64
65    /** Сверяет состояние формы с целями: вызывается после каждого изменения. */
66    function checkProgress() {
67        var zone = document.querySelector('[name="shipping[variant_id]"]');
68        if (zone && zone.value) {
69            if (initial_zone === null) {
70                initial_zone = zone.value;
71            } else if (zone.value !== initial_zone) {
72                goal('CHECKOUT_ZONE_CHANGED');
73            }
74        }
75        if (filled('[name="details[shipping_address][street]"]')) {
76            goal('CHECKOUT_ADDRESS');
77        }
78        if (filled('[name="details[custom][desired_delivery.interval]"]')
79            || filled('[name="details[custom][desired_delivery.date]"]')) {
80            goal('CHECKOUT_DATETIME');
81        }
82        if (filled('[name="auth[data][phone]"]')) {
83            goal('CHECKOUT_CONTACTS');
84        }
85    }
86
87    function watchCheckout(wrapper) {
88        goal('CHECKOUT_OPEN');
89
90        document.addEventListener('change', checkProgress, true);
91        document.addEventListener('blur', checkProgress, true);
92
93        document.addEventListener('click', function (e) {
94            if (e.target.closest && e.target.closest('.js-submit-order-button, .js-submit-confirm')) {
95                goal('CHECKOUT_SUBMIT');
96            }
97        }, true);
98
99        // Секции формы перерисовываются ответом сервера, без событий на полях:
100        // зону доставки и ошибки видно только по изменению разметки. Перерисовка
101        // идёт пачками, поэтому проверяем не чаще раза в 300 мс.
102        var recheck_timer = null;
103        new MutationObserver(function () {
104            if (!recheck_timer) {
105                recheck_timer = setTimeout(function () {
106                    recheck_timer = null;
107                    checkProgress();
108                }, 300);
109            }
110
111            var error = document.querySelector('.wa-error-text, .wa-error');
112            if (!error) { return; }
113            var section = error.closest('[class*="wa-step-"]');
114            goal('CHECKOUT_ERROR', {
115                section: section ? (section.className.match(/wa-step-([a-z]+)-section/) || [])[1] : 'unknown',
116                text: (error.textContent || '').trim().slice(0, 100)
117            });
118        }).observe(wrapper, { childList: true, subtree: true });
119
120        checkProgress();
121    }
122
123    function init() {
124        // Скрипт глобальный, но нужен только на оформлении и странице успеха.
125        if (!/\/(order|checkout)(\/|$)/.test(location.pathname)) { return; }
126
127        if (/\/checkout\/success/.test(location.pathname)
128            || document.querySelector('.wa-order-success, .order-success')) {
129            goal('CHECKOUT_SUCCESS_PAGE');
130            return;
131        }
132        var wrapper = document.querySelector('.wa-order-form-wrapper');
133        if (wrapper) { watchCheckout(wrapper); return; }
134
135        // Форма оформления дорисовывается скриптом — ждём её появления.
136        var waiting_form = new MutationObserver(function () {
137            var w = document.querySelector('.wa-order-form-wrapper');
138            if (w) { waiting_form.disconnect(); watchCheckout(w); }
139        });
140        waiting_form.observe(document.body, { childList: true, subtree: true });
141        setTimeout(function () { waiting_form.disconnect(); }, QUEUE_LIMIT_MS);
142    }
143
144    if (document.readyState === 'loading') {
145        document.addEventListener('DOMContentLoaded', init);
146    } else {
147        init();
148    }
149})();

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.