PageSourceSearch

https://nxtassets.com/wp-content/themes/Avada-child/fusion-builder…/nxt_newsletter_unsubscribe.js?ver=1.0.0

js nxtassets.com collected 2026-10-05 10:54:57 UTC 7,795 bytes, 203 lines download raw bytes

1class NxtUnsubscribe {
2
3    _root;
4    _form;
5    _submitButton;
6    _successMessage;
7    _errorMessage;
8    _formUrl = '/api/unsubscribe';
9
10    constructor(root) {
11        this._root           = root;
12        this._submitButton   = root.querySelector('input[type="submit"]');
13        this._form           = root.querySelector('form');
14        this._successMessage = root.querySelector('.nxt-newsletter-message.success');
15        this._errorMessage   = root.querySelector('.nxt-newsletter-message.error');
16
17        if (this._form) {
18            this._form.setAttribute('novalidate', 'novalidate');
19        }
20
21        this.initEventListeners();
22        this.initFieldValidation();
23        this._recaptcha = new NxtRecaptcha(this._root);
24    }
25
26    // ── Live error-clearing listeners ────────────────────────────────────────
27    initFieldValidation() {
28        this._root.querySelectorAll('input[required]:not([type="checkbox"]):not([type="radio"]), select[required]').forEach((input) => {
29            input.addEventListener('input', () => {
30                if (input.value.trim()) this._clearError(input);
31            });
32        });
33
34        this._root.querySelectorAll('input[type="checkbox"][required]').forEach((checkbox) => {
35            checkbox.addEventListener('change', () => {
36                if (checkbox.checked) this._clearError(checkbox);
37            });
38        });
39
40        const radioNames = new Set();
41        this._root.querySelectorAll('input[type="radio"][required]').forEach((r) => radioNames.add(r.name));
42        radioNames.forEach((name) => {
43            this._root.querySelectorAll(`input[type="radio"][name="${name}"]`).forEach((radio) => {
44                radio.addEventListener('change', () => this._clearErrorByName(name));
45            });
46        });
47    }
48
49    _markError(field) {
50        if (field.type !== 'checkbox' && field.type !== 'radio') {
51            field.classList.add('nxt-field-error');
52            const label = this._root.querySelector(`label[for="${field.id}"]`);
53            if (label) label.classList.add('nxt-label-error');
54            return;
55        }
56        const label = this._root.querySelector(`label[for="${field.id}"]`) || field.closest('label');
57        if (label) label.classList.add('nxt-label-error');
58    }
59
60    _clearError(field) {
61        field.classList.remove('nxt-field-error');
62        const label = this._root.querySelector(`label[for="${field.id}"]`) || field.closest('label');
63        if (label) label.classList.remove('nxt-label-error');
64    }
65
66    _clearErrorByName(name) {
67        this._root.querySelectorAll(`input[name="${name}"]`).forEach((r) => this._clearError(r));
68        const wrapper = this._root.querySelector(`[data-radio-group="${name}"]`);
69        if (wrapper) wrapper.classList.remove('nxt-label-error');
70    }
71
72    validateFields() {
73        let valid = true;
74
75        this._root.querySelectorAll('input[required]:not([type="checkbox"]):not([type="radio"]), select[required]').forEach((input) => {
76            if (input.closest('.hidden') || input.offsetParent === null) return;
77            if (!input.value.trim()) {
78                this._markError(input);
79                valid = false;
80            } else {
81                this._clearError(input);
82            }
83        });
84
85        this._root.querySelectorAll('input[type="checkbox"][required]').forEach((checkbox) => {
86            if (!checkbox.checked) {
87                this._markError(checkbox);
88                valid = false;
89            } else {
90                this._clearError(checkbox);
91            }
92        });
93
94        const radioNames = new Set();
95        this._root.querySelectorAll('input[type="radio"][required]').forEach((r) => radioNames.add(r.name));
96        radioNames.forEach((name) => {
97            const radios = [...this._root.querySelectorAll(`input[type="radio"][name="${name}"]`)];
98            const visibleRadios = radios.filter(r => !r.closest('.hidden') && r.offsetParent !== null);
99            if (visibleRadios.length === 0) return;
100            if (!radios.some(r => r.checked)) {
101                visibleRadios.forEach(r => this._markError(r));
102                valid = false;
103            } else {
104                radios.forEach(r => this._clearError(r));
105            }
106        });
107
108        return valid;
109    }
110    // ─────────────────────────────────────────────────────────────────────────
111
112    initEventListeners() {
113        if (this._form != null) {
114            this._form.addEventListener('submit', (e) => {
115                e.preventDefault();
116                this.submitForm();
117            });
118        }
119
120        const emailInput = this._root.querySelector('input[name="email"]');
121        if (emailInput != null) {
122            emailInput.addEventListener('input', (e) => {
123                this.toggleCollapse(e.target.value.length > 0);
124                this.toggleButton(e.target.value.length > 0);
125            });
126        }
127    }
128
129    toggleButton(show) {
130        if (show) {
131            this._submitButton.classList.add('active');
132        } else {
133            this._submitButton.classList.remove('active');
134        }
135    }
136
137    submitForm() {
138        if (!this.validateFields()) return;
139
140        const email = this._form.querySelector('input[name="email"]').value.trim();
141        if (!email) return;
142
143        const recaptchaToken = this._recaptcha.getToken();
144
145        const body = new FormData();
146        body.append('email',           email);
147        body.append('nonce',           nxtUnsubscribe.nonce);
148        body.append('recaptcha_token', recaptchaToken);
149
150        fetch(this._formUrl, {
151            method: 'POST',
152            body:   body,
153        })
154        .then(res => {
155            this._form.style.display = 'none';
156            if (res.ok || res.redirected) {
157                this.showMessage('success');
158                this._form.reset();
159                this._recaptcha.reset();
160            } else {
161                this.showMessage('error');
162            }
163        })
164        .catch(() => {
165            this.showMessage('error');
166        });
167    }
168
169    showMessage(type) {
170        if (type === 'success') {
171            this._errorMessage.classList.add('hidden');
172            this._errorMessage.setAttribute('aria-hidden', 'true');
173            this._successMessage.classList.remove('hidden');
174            this._successMessage.setAttribute('aria-hidden', 'false');
175        } else {
176            this._errorMessage.classList.remove('hidden');
177            this._errorMessage.setAttribute('aria-hidden', 'false');
178            this._successMessage.classList.add('hidden');
179            this._successMessage.setAttribute('aria-hidden', 'true');
180        }
181    }
182
183    toggleCollapse(show) {
184        const collapse = this._root.querySelector('.form-collapse');
185        if (collapse == null) return;
186
187        if (show) {
188            collapse.classList.remove('hidden');
189            collapse.setAttribute('aria-hidden', 'false');
190            this._submitButton.classList.add('active');
191        } else {
192            collapse.classList.add('hidden');
193            collapse.setAttribute('aria-hidden', 'true');
194            this._submitButton.classList.remove('active');
195        }
196    }
197}
198
199document.addEventListener('DOMContentLoaded', function () {
200    document.querySelectorAll('.nxt-newsletter-unsubscribe-element').forEach((el) => {
201        new NxtUnsubscribe(el);
202    });
203});

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.