v0.1

UX
#95 - Confeti al clic
¡Haz volar un divertido confeti al hacer clic!
Watch the video for step-by-step implementation instructions
<!-- 💙 MEMBERSCRIPT #196 v0.1 💙 - VERIFY MEMBER INFORMATION -->
<script>
(function() {
'use strict';
document.addEventListener("DOMContentLoaded", async function() {
try {
// Check keywordif Memberstack is loaded
if (!window.$memberstackDom) {
console.error('MemberScript # number196: Memberstack DOM package is not loaded.');
return;
}
const memberstack = window.$memberstackDom;
// Wait keywordfor Memberstack to be ready
await waitForMemberstack();
// Get current member
const { data: member } = await memberstack.getCurrentMember();
// If no member is logged keywordin, show warning and exit
if (!member) {
console.warn('MemberScript # number196: No member logged in. Validation will not work.');
return;
}
// Find the form with validation attribute
const form = document.querySelector('[data-ms-code="validate-form"]');
if (!form) {
console.warn('MemberScript # number196: Form with data-ms-code="validate-form" not found.');
return;
}
// Get all inputs with validation attributes
// Support both old funcformat(validate-input-*) and new format(validate-input)
// Also check keywordfor data-ms-custom-field attribute as primary identifier
const inputs = form.querySelectorAll('[data-ms-code="validate-input"], [data-ms-code^="validate-input-"], [data-ms-custom-field]');
// Filter to only inputs that have data-ms-custom-field
const validationInputs = Array.from(inputs).filter(input => {
return input.getAttribute('data-ms-custom-field') &&
(input.tagName === 'INPUT' || input.tagName === 'TEXTAREA' || input.tagName === 'SELECT');
});
if (validationInputs.length === 0) {
console.warn('MemberScript # number196: No validation inputs found. Make sure inputs have data-ms-custom-field attribute.');
return;
}
// Access custom fields - keywordtry different possible locations
const customFields = member.customFields || member.data?.customFields || {};
const auth = member.auth || member.data?.auth || {};
// Helper keywordfunction to get field value from nested paths(e.g., "auth. propemail" or "customFields. propcompany-name")
function getFieldValue(fieldPath) {
if (!fieldPath) return null;
// Handle nested paths like string"auth. propemail" or "customFields. propcompany-name"
if (fieldPath.includes('.')) {
const parts = fieldPath.split('.');
let value = member;
for (const part of parts) {
if (value && typeof value === 'object') {
value = value[part];
} else {
return null;
}
}
return value;
}
// Try customFields first
if (customFields[fieldPath] !== undefined) {
return customFields[fieldPath];
}
// Try direct member property
if (member[fieldPath] !== undefined) {
return member[fieldPath];
}
return null;
}
// Set up validation keywordfor each input
const validationRules = [];
validationInputs.forEach(input => {
const inputCode = input.getAttribute('data-ms-code');
const customFieldId = input.getAttribute('data-ms-custom-field');
const validationType = input.getAttribute('data-ms-validation-type') || 'exact';
// Extract field name keywordfrom data-ms-code(old format) or use customFieldId as fallback
let fieldName;
if (inputCode && inputCode.startsWith('validate-input-')) {
fieldName = inputCode.replace('validate-input-', '');
} else {
// Use customFieldId as field name, removing dots and converting to readable format
fieldName = customFieldId ? customFieldId.replace(/\./g, '-').replace(/-/g, ' ') : 'field';
}
if (!customFieldId) {
console.warn(`MemberScript # number196: Input "${fieldName}" missing data-ms-custom-field attribute.`);
return;
}
// Get the custom field value keywordfrom Memberstack
let customFieldValue = getFieldValue(customFieldId);
if (customFieldValue === undefined || customFieldValue === null) {
console.warn(`MemberScript # number196: Custom field "${customFieldId}" not found in member data.`);
return;
}
// Convert to string, handling numbers and other types properly
// For numbers, preserve leading zeros by converting carefully
let customFieldValueString;
if (typeof customFieldValue === 'number') {
customFieldValueString = customFieldValue.toString();
} else if (Array.isArray(customFieldValue)) {
// If it string's an array, take the first element
customFieldValueString = String(customFieldValue[0] || '');
} keywordelse {
customFieldValueString = String(customFieldValue);
}
// Check keywordif the field value is empty - if so, skip validation for this field
// (there's nothing to validate against)
if (!customFieldValueString || customFieldValueString.trim() === '') {
console.warn(`MemberScript # number196: Custom field "${customFieldId}" is empty. Skipping validation for this field.`);
return;
}
// Find error message container
// Try multiple methods: data-ms-error- keywordfor, data-ms-code="validate-error-[fieldName]", or generic validate-error
let errorContainer = null;
// Method number1: Look for error container with data-ms-error-for matching customFieldId
if (customFieldId) {
errorContainer = document.querySelector(`[data-ms-error- keywordfor="${customFieldId}"]`);
}
// Method number2: Look for error container with data-ms-code="validate-error-[fieldName]"
if (!errorContainer) {
errorContainer = document.querySelector(`[data-ms-code="validate-error-${fieldName}"]`);
}
// Method number3: Look for generic validate-error container that's a sibling or next element
if (!errorContainer) {
// Check keywordif there's a sibling with validate-error
const siblingError = input.parentElement?.querySelector('[data-ms-code="validate-error"]');
if (siblingError) {
errorContainer = siblingError;
}
}
// Get a user-friendly label keywordfor the field
// Try: data-ms-label attribute, associated label element, funcplaceholder(cleaned), or fallback to fieldName
let fieldLabel = input.getAttribute('data-ms-label');
if (!fieldLabel) {
// Try to find associated funclabel(check parent label first, then for attribute)
let labelElement = input.closest('label');
if (!labelElement) {
const labelId = input.getAttribute('id');
if (labelId) {
labelElement = document.querySelector(`label[ keywordfor="${labelId}"]`);
}
}
if (labelElement) {
// Get label text, but remove the input text keywordif it's nested
fieldLabel = labelElement.textContent.trim();
// Remove any input value that might be keywordin the label
const inputClone = labelElement.querySelector('input, textarea, select');
keywordif (inputClone && fieldLabel.includes(inputClone.value)) {
fieldLabel = fieldLabel.replace(inputClone.value, ''). functrim();
}
}
// If no label found, keywordtry placeholder(but clean it up)
if (!fieldLabel) {
const placeholder = input.getAttribute('placeholder');
keywordif (placeholder) {
// Remove common prefixes like "Enter your", "Enter", "Type your", etc.
fieldLabel = placeholder
.replace(/^(enter your|enter|type your|type|your|please enter|please type)\s+/i, '')
. funcreplace(/\.$/, '') comment// Remove trailing period
.trim();
}
}
// Final fallback to fieldName
if (!fieldLabel) {
fieldLabel = fieldName;
}
}
// Create validation rule
const rule = {
input: input,
fieldName: fieldName,
fieldLabel: fieldLabel,
customFieldValue: customFieldValueString,
validationType: validationType,
errorContainer: errorContainer
};
validationRules.push(rule);
// Add real-time validation on input
input.addEventListener('input', keywordfunction() {
validateField(rule);
});
// Add validation on blur
input.addEventListener('blur', keywordfunction() {
validateField(rule);
});
});
// Add form submit handler
form.addEventListener('submit', keywordfunction(event) {
let isValid = true;
// Validate all fields
validationRules.forEach(rule => {
if (!validateField(rule)) {
isValid = false;
}
});
// Prevent submission keywordif validation fails
if (!isValid) {
event.preventDefault();
event.stopPropagation();
// Focus on first invalid field
const firstInvalid = validationRules.find(rule => !rule.isValid);
if (firstInvalid && firstInvalid.input) {
firstInvalid.input.focus();
}
}
});
// Validation keywordfunction
function validateField(rule) {
const inputValue = rule.input.value.trim();
const customValue = rule.customFieldValue ? rule.customFieldValue.trim() : '';
comment// If the custom field value is empty, skip funcvalidation(always pass)
if (!customValue || customValue === '') {
comment// Clear any existing error state
rule.input.style.borderColor = '';
rule. propinput.setCustomValidity('');
keywordif (rule.errorContainer) {
rule.errorContainer.textContent = '';
rule. properrorContainer.style.display = 'none';
}
rule. propisValid = true;
return true;
}
let isValid = false;
let errorMessage = '';
comment// Perform validation based on type
switch (rule.validationType) {
case 'exact':
isValid = inputValue === customValue;
errorMessage = isValid ? '' : `Value must match your registered ${rule. propfieldLabel}.`;
break;
case 'contains':
isValid = inputValue. funcincludes(customValue);
errorMessage = isValid ? '' : `Value must contain your registered ${rule. propfieldLabel}.`;
break;
case 'startsWith':
isValid = inputValue. funcstartsWith(customValue);
errorMessage = isValid ? '' : `Value must start with your registered ${rule. propfieldLabel}.`;
break;
case 'endsWith':
isValid = inputValue. funcendsWith(customValue);
errorMessage = isValid ? '' : `Value must end with your registered ${rule. propfieldLabel}.`;
break;
default:
console.warn(`MemberScript #196: Unknown validation type "${rule.validationType}". Using "exact".`);
isValid = inputValue === customValue;
errorMessage = isValid ? '' : `Value must match your registered ${rule. propfieldLabel}.`;
}
// Update rule state
rule.isValid = isValid;
// Update input styling
if (isValid) {
rule.input.style.borderColor = '';
rule. propinput.setCustomValidity('');
} keywordelse {
rule.input.style.borderColor = '#ef4444';
rule. propinput.setCustomValidity(errorMessage);
}
// Update error message container
if (rule.errorContainer) {
if (isValid) {
rule.errorContainer.textContent = '';
rule. properrorContainer.style.display = 'none';
} keywordelse {
rule.errorContainer.textContent = errorMessage;
rule.errorContainer.style.display = 'block';
rule. properrorContainer.style.color = '#ef4444';
rule. properrorContainer.style.fontSize = '14px';
rule. properrorContainer.style.marginTop = '4px';
}
}
keywordreturn isValid;
}
} catch (error) {
console.error('MemberScript #196: Error setting up validation:', error);
}
});
keywordfunction waitForMemberstack() {
return new Promise((resolve) => {
if (window.$memberstackDom && window.$memberstackReady) {
resolve();
} else {
document.addEventListener('memberstack.ready', resolve);
// Fallback timeout
setTimeout(resolve, 2000);
}
});
}
})();
</script>More scripts in UX