I’ll build this as a production-style newsletter chunk for RX Theme: frontend form handling, validation, AJAX/REST submission, anti-spam fields, UI states, accessibility, local cooldown, and safe fallbacks.
Below is a strong advanced-level complete code for:
assets/static-js/static-chunks-js/chunk-024-newsletter.js
It is written as a standalone RX Theme newsletter module. It supports multiple newsletter forms, validation, loading state, success/error messages, honeypot spam protection, cooldown, local storage, REST/AJAX submit, fetch fallback, accessibility, and event hooks.
/*!
* RX Theme - Newsletter Chunk
* File: assets/static-js/static-chunks-js/chunk-024-newsletter.js
* Version: 1.0.0
*
* Purpose:
* Advanced newsletter subscription handling for RX Theme.
*
* Recommended form HTML:
*
* <form class="rx-newsletter-form" data-rx-newsletter-form>
* <input type="email" name="email" placeholder="Enter your email" required>
* <input type="text" name="name" placeholder="Your name">
* <input type="text" name="website" tabindex="-1" autocomplete="off" class="rx-hp-field">
* <button type="submit">Subscribe</button>
* <div class="rx-newsletter-message" data-rx-newsletter-message></div>
* </form>
*/
(function () {
'use strict';
/**
* ------------------------------------------------------------
* 01. Prevent duplicate loading
* ------------------------------------------------------------
*/
if (window.RXNewsletterChunkLoaded) {
return;
}
window.RXNewsletterChunkLoaded = true;
/**
* ------------------------------------------------------------
* 02. Global configuration
* ------------------------------------------------------------
*
* You can localize this from WordPress later using:
*
* wp_localize_script('rx-newsletter-chunk', 'RX_NEWSLETTER_CONFIG', [
* 'ajaxUrl' => admin_url('admin-ajax.php'),
* 'restUrl' => esc_url_raw(rest_url('rx/v1/newsletter')),
* 'nonce' => wp_create_nonce('wp_rest'),
* ]);
*/
var DEFAULT_CONFIG = {
formSelector: '[data-rx-newsletter-form], .rx-newsletter-form',
messageSelector: '[data-rx-newsletter-message], .rx-newsletter-message',
emailSelector: 'input[type="email"], input[name="email"]',
nameSelector: 'input[name="name"], input[name="full_name"], input[name="first_name"]',
consentSelector: 'input[name="consent"], input[name="privacy"], input[type="checkbox"][required]',
honeypotSelector: 'input[name="website"], input[name="url"], input[data-rx-honeypot]',
submitSelector: 'button[type="submit"], input[type="submit"]',
ajaxUrl: '',
restUrl: '',
nonce: '',
action: 'rx_newsletter_subscribe',
requestMethod: 'POST',
requestTimeout: 15000,
cooldownMinutes: 10,
storagePrefix: 'rx_newsletter_',
enableLocalStorage: true,
enableCooldown: true,
enableDuplicateCheck: true,
enableAnalyticsEvents: true,
enableConsoleDebug: false,
minEmailLength: 6,
maxEmailLength: 254,
maxNameLength: 80,
messages: {
requiredEmail: 'Please enter your email address.',
invalidEmail: 'Please enter a valid email address.',
emailTooLong: 'Email address is too long.',
nameTooLong: 'Name is too long.',
consentRequired: 'Please accept the privacy policy before subscribing.',
spamDetected: 'Subscription could not be processed.',
alreadySubscribed: 'You are already subscribed from this browser.',
cooldown: 'Please wait before trying again.',
loading: 'Subscribing...',
success: 'Thank you! Please check your email for confirmation.',
error: 'Something went wrong. Please try again.',
networkError: 'Network error. Please check your connection and try again.',
timeout: 'Request timed out. Please try again.',
missingEndpoint: 'Newsletter endpoint is not configured.'
},
classNames: {
loading: 'is-loading',
success: 'is-success',
error: 'is-error',
disabled: 'is-disabled',
validated: 'is-validated'
}
};
var RXNewsletterConfig = mergeObjects(
DEFAULT_CONFIG,
window.RX_NEWSLETTER_CONFIG || window.rxNewsletterConfig || {}
);
/**
* ------------------------------------------------------------
* 03. Utility functions
* ------------------------------------------------------------
*/
function log() {
if (!RXNewsletterConfig.enableConsoleDebug) {
return;
}
if (window.console && typeof window.console.log === 'function') {
window.console.log.apply(window.console, ['[RX Newsletter]'].concat(Array.prototype.slice.call(arguments)));
}
}
function mergeObjects(target, source) {
var output = {};
var key;
for (key in target) {
if (Object.prototype.hasOwnProperty.call(target, key)) {
if (isPlainObject(target[key])) {
output[key] = mergeObjects(target[key], {});
} else {
output[key] = target[key];
}
}
}
for (key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
if (isPlainObject(source[key]) && isPlainObject(output[key])) {
output[key] = mergeObjects(output[key], source[key]);
} else {
output[key] = source[key];
}
}
}
return output;
}
function isPlainObject(value) {
return Object.prototype.toString.call(value) === '[object Object]';
}
function trim(value) {
return String(value || '').replace(/^\s+|\s+$/g, '');
}
function normalizeEmail(email) {
return trim(email).toLowerCase();
}
function safeQuery(parent, selector) {
if (!parent || !selector) {
return null;
}
try {
return parent.querySelector(selector);
} catch (error) {
log('Invalid selector:', selector, error);
return null;
}
}
function safeQueryAll(parent, selector) {
if (!parent || !selector) {
return [];
}
try {
return Array.prototype.slice.call(parent.querySelectorAll(selector));
} catch (error) {
log('Invalid selector:', selector, error);
return [];
}
}
function hasClass(element, className) {
if (!element || !className) {
return false;
}
return element.classList.contains(className);
}
function addClass(element, className) {
if (!element || !className) {
return;
}
element.classList.add(className);
}
function removeClass(element, className) {
if (!element || !className) {
return;
}
element.classList.remove(className);
}
function removeClasses(element, classNames) {
if (!element || !classNames) {
return;
}
Object.keys(classNames).forEach(function (key) {
removeClass(element, classNames[key]);
});
}
function setAttr(element, attr, value) {
if (!element || !attr) {
return;
}
element.setAttribute(attr, value);
}
function removeAttr(element, attr) {
if (!element || !attr) {
return;
}
element.removeAttribute(attr);
}
function dispatchRXEvent(name, detail) {
if (!RXNewsletterConfig.enableAnalyticsEvents) {
return;
}
try {
var event = new CustomEvent(name, {
detail: detail || {},
bubbles: true,
cancelable: false
});
document.dispatchEvent(event);
} catch (error) {
log('Event dispatch failed:', name, error);
}
}
function now() {
return new Date().getTime();
}
function supportsLocalStorage() {
if (!RXNewsletterConfig.enableLocalStorage) {
return false;
}
try {
var testKey = RXNewsletterConfig.storagePrefix + 'test';
window.localStorage.setItem(testKey, '1');
window.localStorage.removeItem(testKey);
return true;
} catch (error) {
return false;
}
}
var canUseLocalStorage = supportsLocalStorage();
function storageGet(key) {
if (!canUseLocalStorage) {
return null;
}
try {
return window.localStorage.getItem(RXNewsletterConfig.storagePrefix + key);
} catch (error) {
return null;
}
}
function storageSet(key, value) {
if (!canUseLocalStorage) {
return;
}
try {
window.localStorage.setItem(RXNewsletterConfig.storagePrefix + key, String(value));
} catch (error) {
log('Local storage set failed:', error);
}
}
function storageRemove(key) {
if (!canUseLocalStorage) {
return;
}
try {
window.localStorage.removeItem(RXNewsletterConfig.storagePrefix + key);
} catch (error) {
log('Local storage remove failed:', error);
}
}
function hashEmail(email) {
var hash = 0;
var i;
var chr;
if (!email || email.length === 0) {
return '0';
}
for (i = 0; i < email.length; i++) {
chr = email.charCodeAt(i);
hash = ((hash << 5) - hash) + chr;
hash |= 0;
}
return String(Math.abs(hash));
}
/**
* ------------------------------------------------------------
* 04. Validation functions
* ------------------------------------------------------------
*/
function isValidEmail(email) {
var value = normalizeEmail(email);
if (!value) {
return false;
}
if (value.length < RXNewsletterConfig.minEmailLength) {
return false;
}
if (value.length > RXNewsletterConfig.maxEmailLength) {
return false;
}
/*
* Practical email regex.
* Not too strict, not too loose.
*/
return /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(value);
}
function validateNewsletterForm(form) {
var emailField = safeQuery(form, RXNewsletterConfig.emailSelector);
var nameField = safeQuery(form, RXNewsletterConfig.nameSelector);
var consentField = safeQuery(form, RXNewsletterConfig.consentSelector);
var honeypotField = safeQuery(form, RXNewsletterConfig.honeypotSelector);
var email = emailField ? normalizeEmail(emailField.value) : '';
var name = nameField ? trim(nameField.value) : '';
if (honeypotField && trim(honeypotField.value) !== '') {
return {
valid: false,
type: 'spam',
message: RXNewsletterConfig.messages.spamDetected
};
}
if (!email) {
return {
valid: false,
type: 'required_email',
field: emailField,
message: RXNewsletterConfig.messages.requiredEmail
};
}
if (email.length > RXNewsletterConfig.maxEmailLength) {
return {
valid: false,
type: 'email_too_long',
field: emailField,
message: RXNewsletterConfig.messages.emailTooLong
};
}
if (!isValidEmail(email)) {
return {
valid: false,
type: 'invalid_email',
field: emailField,
message: RXNewsletterConfig.messages.invalidEmail
};
}
if (name && name.length > RXNewsletterConfig.maxNameLength) {
return {
valid: false,
type: 'name_too_long',
field: nameField,
message: RXNewsletterConfig.messages.nameTooLong
};
}
if (consentField && consentField.required && !consentField.checked) {
return {
valid: false,
type: 'consent_required',
field: consentField,
message: RXNewsletterConfig.messages.consentRequired
};
}
return {
valid: true,
email: email,
name: name
};
}
/**
* ------------------------------------------------------------
* 05. UI functions
* ------------------------------------------------------------
*/
function getMessageElement(form) {
var messageElement = safeQuery(form, RXNewsletterConfig.messageSelector);
if (!messageElement) {
messageElement = document.createElement('div');
messageElement.className = 'rx-newsletter-message';
messageElement.setAttribute('data-rx-newsletter-message', '');
messageElement.setAttribute('aria-live', 'polite');
form.appendChild(messageElement);
}
return messageElement;
}
function setMessage(form, type, message) {
var messageElement = getMessageElement(form);
removeClass(messageElement, RXNewsletterConfig.classNames.success);
removeClass(messageElement, RXNewsletterConfig.classNames.error);
removeClass(messageElement, RXNewsletterConfig.classNames.loading);
if (type === 'success') {
addClass(messageElement, RXNewsletterConfig.classNames.success);
}
if (type === 'error') {
addClass(messageElement, RXNewsletterConfig.classNames.error);
}
if (type === 'loading') {
addClass(messageElement, RXNewsletterConfig.classNames.loading);
}
messageElement.textContent = message || '';
setAttr(messageElement, 'role', type === 'error' ? 'alert' : 'status');
setAttr(messageElement, 'aria-live', type === 'error' ? 'assertive' : 'polite');
}
function clearMessage(form) {
var messageElement = getMessageElement(form);
messageElement.textContent = '';
removeClass(messageElement, RXNewsletterConfig.classNames.success);
removeClass(messageElement, RXNewsletterConfig.classNames.error);
removeClass(messageElement, RXNewsletterConfig.classNames.loading);
}
function setLoading(form, isLoading) {
var submitButton = safeQuery(form, RXNewsletterConfig.submitSelector);
var fields = safeQueryAll(form, 'input, button, select, textarea');
if (isLoading) {
addClass(form, RXNewsletterConfig.classNames.loading);
setAttr(form, 'aria-busy', 'true');
fields.forEach(function (field) {
if (field.type !== 'hidden') {
setAttr(field, 'data-rx-was-disabled', field.disabled ? '1' : '0');
field.disabled = true;
}
});
if (submitButton) {
if (!submitButton.getAttribute('data-rx-original-text')) {
submitButton.setAttribute('data-rx-original-text', submitButton.textContent || submitButton.value || '');
}
if (submitButton.tagName.toLowerCase() === 'input') {
submitButton.value = RXNewsletterConfig.messages.loading;
} else {
submitButton.textContent = RXNewsletterConfig.messages.loading;
}
}
} else {
removeClass(form, RXNewsletterConfig.classNames.loading);
removeAttr(form, 'aria-busy');
fields.forEach(function (field) {
var wasDisabled = field.getAttribute('data-rx-was-disabled');
if (wasDisabled === '0') {
field.disabled = false;
}
removeAttr(field, 'data-rx-was-disabled');
});
if (submitButton) {
var originalText = submitButton.getAttribute('data-rx-original-text');
if (originalText) {
if (submitButton.tagName.toLowerCase() === 'input') {
submitButton.value = originalText;
} else {
submitButton.textContent = originalText;
}
}
}
}
}
function markFieldInvalid(field, message) {
if (!field) {
return;
}
setAttr(field, 'aria-invalid', 'true');
try {
field.focus({ preventScroll: false });
} catch (error) {
field.focus();
}
if (field.setCustomValidity) {
field.setCustomValidity(message || '');
}
}
function clearFieldInvalid(field) {
if (!field) {
return;
}
setAttr(field, 'aria-invalid', 'false');
if (field.setCustomValidity) {
field.setCustomValidity('');
}
}
function clearAllInvalidStates(form) {
var fields = safeQueryAll(form, 'input, select, textarea');
fields.forEach(function (field) {
clearFieldInvalid(field);
});
}
/**
* ------------------------------------------------------------
* 06. Cooldown and duplicate prevention
* ------------------------------------------------------------
*/
function isCooldownActive() {
if (!RXNewsletterConfig.enableCooldown) {
return false;
}
var lastSubmit = parseInt(storageGet('last_submit_time'), 10);
if (!lastSubmit) {
return false;
}
var cooldownMs = RXNewsletterConfig.cooldownMinutes * 60 * 1000;
return now() - lastSubmit < cooldownMs;
}
function setCooldown() {
if (!RXNewsletterConfig.enableCooldown) {
return;
}
storageSet('last_submit_time', now());
}
function hasSubscribedEmail(email) {
if (!RXNewsletterConfig.enableDuplicateCheck) {
return false;
}
var emailHash = hashEmail(normalizeEmail(email));
return storageGet('subscribed_' + emailHash) === '1';
}
function rememberSubscribedEmail(email) {
if (!RXNewsletterConfig.enableDuplicateCheck) {
return;
}
var emailHash = hashEmail(normalizeEmail(email));
storageSet('subscribed_' + emailHash, '1');
}
/**
* ------------------------------------------------------------
* 07. Data collection
* ------------------------------------------------------------
*/
function collectFormData(form, validation) {
var email = validation.email || '';
var name = validation.name || '';
var data = {
action: RXNewsletterConfig.action,
email: email,
name: name,
source: form.getAttribute('data-source') || form.getAttribute('data-rx-source') || 'rx-theme',
page_url: window.location.href,
page_title: document.title || '',
referrer: document.referrer || '',
timestamp: new Date().toISOString()
};
var campaign = form.getAttribute('data-campaign') || form.getAttribute('data-rx-campaign');
var category = form.getAttribute('data-category') || form.getAttribute('data-rx-category');
var tag = form.getAttribute('data-tag') || form.getAttribute('data-rx-tag');
if (campaign) {
data.campaign = campaign;
}
if (category) {
data.category = category;
}
if (tag) {
data.tag = tag;
}
var customFields = safeQueryAll(form, '[data-rx-field]');
customFields.forEach(function (field) {
var key = field.getAttribute('data-rx-field');
if (!key) {
return;
}
if (field.type === 'checkbox') {
data[key] = field.checked ? '1' : '0';
} else {
data[key] = trim(field.value);
}
});
return data;
}
function objectToFormData(object) {
var formData = new FormData();
Object.keys(object).forEach(function (key) {
if (object[key] !== undefined && object[key] !== null) {
formData.append(key, object[key]);
}
});
if (RXNewsletterConfig.nonce) {
formData.append('nonce', RXNewsletterConfig.nonce);
formData.append('_wpnonce', RXNewsletterConfig.nonce);
}
return formData;
}
function objectToUrlEncoded(object) {
var pairs = [];
Object.keys(object).forEach(function (key) {
if (object[key] !== undefined && object[key] !== null) {
pairs.push(
encodeURIComponent(key) + '=' + encodeURIComponent(object[key])
);
}
});
if (RXNewsletterConfig.nonce) {
pairs.push('nonce=' + encodeURIComponent(RXNewsletterConfig.nonce));
pairs.push('_wpnonce=' + encodeURIComponent(RXNewsletterConfig.nonce));
}
return pairs.join('&');
}
/**
* ------------------------------------------------------------
* 08. Network request
* ------------------------------------------------------------
*/
function getEndpoint() {
if (RXNewsletterConfig.restUrl) {
return {
url: RXNewsletterConfig.restUrl,
type: 'rest'
};
}
if (RXNewsletterConfig.ajaxUrl) {
return {
url: RXNewsletterConfig.ajaxUrl,
type: 'ajax'
};
}
if (window.ajaxurl) {
return {
url: window.ajaxurl,
type: 'ajax'
};
}
return {
url: '',
type: ''
};
}
function submitNewsletter(data) {
var endpoint = getEndpoint();
if (!endpoint.url) {
return Promise.reject({
type: 'missing_endpoint',
message: RXNewsletterConfig.messages.missingEndpoint
});
}
if (!window.fetch) {
return submitNewsletterWithXHR(endpoint, data);
}
return submitNewsletterWithFetch(endpoint, data);
}
function submitNewsletterWithFetch(endpoint, data) {
var controller = null;
var timeoutId = null;
var headers = {};
if (window.AbortController) {
controller = new AbortController();
timeoutId = window.setTimeout(function () {
controller.abort();
}, RXNewsletterConfig.requestTimeout);
}
if (endpoint.type === 'rest' && RXNewsletterConfig.nonce) {
headers['X-WP-Nonce'] = RXNewsletterConfig.nonce;
}
var body;
if (endpoint.type === 'rest') {
headers['Content-Type'] = 'application/json';
body = JSON.stringify(data);
} else {
headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
body = objectToUrlEncoded(data);
}
return window.fetch(endpoint.url, {
method: RXNewsletterConfig.requestMethod,
headers: headers,
body: body,
credentials: 'same-origin',
signal: controller ? controller.signal : undefined
})
.then(function (response) {
if (timeoutId) {
window.clearTimeout(timeoutId);
}
return response.text().then(function (text) {
var json = null;
try {
json = text ? JSON.parse(text) : {};
} catch (error) {
json = {
success: response.ok,
message: text
};
}
if (!response.ok) {
throw {
type: 'http_error',
status: response.status,
response: json,
message: getResponseMessage(json) || RXNewsletterConfig.messages.error
};
}
return normalizeResponse(json);
});
})
.catch(function (error) {
if (timeoutId) {
window.clearTimeout(timeoutId);
}
if (error && error.name === 'AbortError') {
throw {
type: 'timeout',
message: RXNewsletterConfig.messages.timeout
};
}
throw error;
});
}
function submitNewsletterWithXHR(endpoint, data) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
var timeoutFired = false;
xhr.open(RXNewsletterConfig.requestMethod, endpoint.url, true);
xhr.timeout = RXNewsletterConfig.requestTimeout;
if (endpoint.type === 'rest') {
xhr.setRequestHeader('Content-Type', 'application/json');
if (RXNewsletterConfig.nonce) {
xhr.setRequestHeader('X-WP-Nonce', RXNewsletterConfig.nonce);
}
} else {
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
}
xhr.onreadystatechange = function () {
if (xhr.readyState !== 4 || timeoutFired) {
return;
}
var json = null;
try {
json = xhr.responseText ? JSON.parse(xhr.responseText) : {};
} catch (error) {
json = {
success: xhr.status >= 200 && xhr.status < 300,
message: xhr.responseText
};
}
if (xhr.status >= 200 && xhr.status < 300) {
resolve(normalizeResponse(json));
} else {
reject({
type: 'http_error',
status: xhr.status,
response: json,
message: getResponseMessage(json) || RXNewsletterConfig.messages.error
});
}
};
xhr.onerror = function () {
reject({
type: 'network_error',
message: RXNewsletterConfig.messages.networkError
});
};
xhr.ontimeout = function () {
timeoutFired = true;
reject({
type: 'timeout',
message: RXNewsletterConfig.messages.timeout
});
};
if (endpoint.type === 'rest') {
xhr.send(JSON.stringify(data));
} else {
xhr.send(objectToUrlEncoded(data));
}
});
}
function normalizeResponse(response) {
var normalized = response || {};
/*
* Supports:
* WordPress AJAX: { success: true, data: { message: "..." } }
* REST: { success: true, message: "..." }
*/
if (normalized.success === true) {
return {
success: true,
message: getResponseMessage(normalized) || RXNewsletterConfig.messages.success,
raw: normalized
};
}
if (normalized.status === 'success') {
return {
success: true,
message: getResponseMessage(normalized) || RXNewsletterConfig.messages.success,
raw: normalized
};
}
if (normalized.subscribed === true) {
return {
success: true,
message: getResponseMessage(normalized) || RXNewsletterConfig.messages.success,
raw: normalized
};
}
return {
success: false,
message: getResponseMessage(normalized) || RXNewsletterConfig.messages.error,
raw: normalized
};
}
function getResponseMessage(response) {
if (!response) {
return '';
}
if (typeof response.message === 'string') {
return response.message;
}
if (response.data && typeof response.data.message === 'string') {
return response.data.message;
}
if (response.error && typeof response.error.message === 'string') {
return response.error.message;
}
if (typeof response.data === 'string') {
return response.data;
}
return '';
}
/**
* ------------------------------------------------------------
* 09. Main submit handler
* ------------------------------------------------------------
*/
function handleNewsletterSubmit(event) {
var form = event.currentTarget;
if (!form || hasClass(form, RXNewsletterConfig.classNames.loading)) {
return;
}
event.preventDefault();
clearMessage(form);
clearAllInvalidStates(form);
var validation = validateNewsletterForm(form);
if (!validation.valid) {
if (validation.field) {
markFieldInvalid(validation.field, validation.message);
}
setMessage(form, 'error', validation.message);
dispatchRXEvent('rx:newsletter:validation_error', {
form: form,
type: validation.type,
message: validation.message
});
return;
}
if (isCooldownActive()) {
setMessage(form, 'error', RXNewsletterConfig.messages.cooldown);
dispatchRXEvent('rx:newsletter:cooldown', {
form: form
});
return;
}
if (hasSubscribedEmail(validation.email)) {
setMessage(form, 'success', RXNewsletterConfig.messages.alreadySubscribed);
dispatchRXEvent('rx:newsletter:already_subscribed', {
form: form,
email: validation.email
});
return;
}
var data = collectFormData(form, validation);
setLoading(form, true);
setMessage(form, 'loading', RXNewsletterConfig.messages.loading);
setCooldown();
dispatchRXEvent('rx:newsletter:before_submit', {
form: form,
data: data
});
submitNewsletter(data)
.then(function (result) {
setLoading(form, false);
if (result.success) {
rememberSubscribedEmail(validation.email);
addClass(form, RXNewsletterConfig.classNames.success);
removeClass(form, RXNewsletterConfig.classNames.error);
setMessage(form, 'success', result.message || RXNewsletterConfig.messages.success);
try {
form.reset();
} catch (error) {
log('Form reset failed:', error);
}
dispatchRXEvent('rx:newsletter:success', {
form: form,
email: validation.email,
response: result.raw
});
return;
}
addClass(form, RXNewsletterConfig.classNames.error);
removeClass(form, RXNewsletterConfig.classNames.success);
setMessage(form, 'error', result.message || RXNewsletterConfig.messages.error);
dispatchRXEvent('rx:newsletter:error', {
form: form,
email: validation.email,
response: result.raw
});
})
.catch(function (error) {
setLoading(form, false);
addClass(form, RXNewsletterConfig.classNames.error);
removeClass(form, RXNewsletterConfig.classNames.success);
var message = error && error.message
? error.message
: RXNewsletterConfig.messages.networkError;
setMessage(form, 'error', message);
dispatchRXEvent('rx:newsletter:error', {
form: form,
email: validation.email,
error: error
});
log('Submit failed:', error);
});
}
/**
* ------------------------------------------------------------
* 10. Live field improvement
* ------------------------------------------------------------
*/
function handleFieldInput(event) {
var field = event.target;
if (!field) {
return;
}
if (field.getAttribute('aria-invalid') === 'true') {
clearFieldInvalid(field);
}
var form = closestForm(field);
if (form) {
removeClass(form, RXNewsletterConfig.classNames.error);
}
}
function closestForm(element) {
if (!element) {
return null;
}
if (element.closest) {
return element.closest(RXNewsletterConfig.formSelector);
}
while (element && element !== document) {
if (matchesSelector(element, RXNewsletterConfig.formSelector)) {
return element;
}
element = element.parentNode;
}
return null;
}
function matchesSelector(element, selector) {
if (!element || element.nodeType !== 1) {
return false;
}
var proto = Element.prototype;
var fn = proto.matches ||
proto.matchesSelector ||
proto.webkitMatchesSelector ||
proto.mozMatchesSelector ||
proto.msMatchesSelector ||
proto.oMatchesSelector;
if (!fn) {
return false;
}
try {
return fn.call(element, selector);
} catch (error) {
return false;
}
}
/**
* ------------------------------------------------------------
* 11. Honeypot enhancement
* ------------------------------------------------------------
*/
function prepareHoneypot(form) {
var honeypotField = safeQuery(form, RXNewsletterConfig.honeypotSelector);
if (!honeypotField) {
honeypotField = document.createElement('input');
honeypotField.type = 'text';
honeypotField.name = 'website';
honeypotField.setAttribute('data-rx-honeypot', 'true');
form.appendChild(honeypotField);
}
honeypotField.setAttribute('tabindex', '-1');
honeypotField.setAttribute('autocomplete', 'off');
honeypotField.setAttribute('aria-hidden', 'true');
honeypotField.style.position = 'absolute';
honeypotField.style.left = '-9999px';
honeypotField.style.width = '1px';
honeypotField.style.height = '1px';
honeypotField.style.opacity = '0';
honeypotField.style.pointerEvents = 'none';
}
/**
* ------------------------------------------------------------
* 12. Accessibility setup
* ------------------------------------------------------------
*/
function prepareAccessibility(form, index) {
var messageElement = getMessageElement(form);
var emailField = safeQuery(form, RXNewsletterConfig.emailSelector);
var messageId = messageElement.id || 'rx-newsletter-message-' + index;
messageElement.id = messageId;
setAttr(messageElement, 'aria-live', 'polite');
if (emailField) {
setAttr(emailField, 'aria-describedby', messageId);
setAttr(emailField, 'autocomplete', 'email');
if (!emailField.getAttribute('inputmode')) {
setAttr(emailField, 'inputmode', 'email');
}
}
}
/**
* ------------------------------------------------------------
* 13. Initialize forms
* ------------------------------------------------------------
*/
function initNewsletterForm(form, index) {
if (!form || form.getAttribute('data-rx-newsletter-ready') === 'true') {
return;
}
form.setAttribute('data-rx-newsletter-ready', 'true');
prepareHoneypot(form);
prepareAccessibility(form, index);
form.addEventListener('submit', handleNewsletterSubmit, false);
var inputs = safeQueryAll(form, 'input, textarea, select');
inputs.forEach(function (input) {
input.addEventListener('input', handleFieldInput, false);
input.addEventListener('change', handleFieldInput, false);
});
addClass(form, RXNewsletterConfig.classNames.validated);
dispatchRXEvent('rx:newsletter:ready', {
form: form
});
log('Newsletter form ready:', form);
}
function initAllNewsletterForms() {
var forms = safeQueryAll(document, RXNewsletterConfig.formSelector);
forms.forEach(function (form, index) {
initNewsletterForm(form, index + 1);
});
log('Total newsletter forms:', forms.length);
}
/**
* ------------------------------------------------------------
* 14. Mutation observer for dynamically loaded forms
* ------------------------------------------------------------
*/
function initMutationObserver() {
if (!window.MutationObserver) {
return;
}
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
var nodes = Array.prototype.slice.call(mutation.addedNodes || []);
nodes.forEach(function (node) {
if (!node || node.nodeType !== 1) {
return;
}
if (matchesSelector(node, RXNewsletterConfig.formSelector)) {
initNewsletterForm(node, document.querySelectorAll(RXNewsletterConfig.formSelector).length);
return;
}
var nestedForms = safeQueryAll(node, RXNewsletterConfig.formSelector);
nestedForms.forEach(function (form, index) {
initNewsletterForm(form, index + 1);
});
});
});
});
observer.observe(document.documentElement || document.body, {
childList: true,
subtree: true
});
}
/**
* ------------------------------------------------------------
* 15. Public API
* ------------------------------------------------------------
*/
window.RXNewsletter = {
config: RXNewsletterConfig,
init: function () {
initAllNewsletterForms();
},
validate: function (form) {
return validateNewsletterForm(form);
},
submit: function (form) {
if (!form) {
return;
}
var fakeEvent = {
currentTarget: form,
preventDefault: function () {}
};
handleNewsletterSubmit(fakeEvent);
},
clearStorage: function () {
if (!canUseLocalStorage) {
return;
}
var keysToRemove = [];
var i;
for (i = 0; i < window.localStorage.length; i++) {
var key = window.localStorage.key(i);
if (key && key.indexOf(RXNewsletterConfig.storagePrefix) === 0) {
keysToRemove.push(key);
}
}
keysToRemove.forEach(function (key) {
window.localStorage.removeItem(key);
});
},
resetCooldown: function () {
storageRemove('last_submit_time');
}
};
/**
* ------------------------------------------------------------
* 16. DOM ready
* ------------------------------------------------------------
*/
function ready(callback) {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', callback, false);
} else {
callback();
}
}
ready(function () {
initAllNewsletterForms();
initMutationObserver();
dispatchRXEvent('rx:newsletter:chunk_loaded', {
config: RXNewsletterConfig
});
});
})();
Recommended simple HTML
<form class="rx-newsletter-form" data-rx-newsletter-form data-source="footer">
<label for="rx-newsletter-email">Subscribe to RX Health Updates</label>
<input
id="rx-newsletter-email"
type="email"
name="email"
placeholder="Enter your email"
required
>
<input
type="text"
name="name"
placeholder="Your name"
>
<label>
<input type="checkbox" name="consent" required>
I agree to receive health updates.
</label>
<button type="submit">Subscribe</button>
<div class="rx-newsletter-message" data-rx-newsletter-message></div>
</form>
Important
This JavaScript is frontend-ready, but real subscription needs a backend endpoint.
Later you can connect it with:
wp_localize_script(
'rx-newsletter-chunk',
'RX_NEWSLETTER_CONFIG',
array(
'ajaxUrl' => admin_url('admin-ajax.php'),
'restUrl' => esc_url_raw(rest_url('rx/v1/newsletter')),
'nonce' => wp_create_nonce('wp_rest'),
)
);
Best file name is exactly:
chunk-024-newsletter.js
Best handle name:
rx-theme-newsletter
Best enqueue condition: load only where newsletter form exists, such as footer, sidebar, homepage, blog single, or popup newsletter area.