v0.1

FormsUX
#204 - Last Profile Update Timestamp
Automatically tracks and displays when members last updated their profile
Let members save simple values, grouped fields, or growing lists into Data Tables.
Watch the video for step-by-step implementation instructions
<!-- 💙 MEMBERSCRIPT #198 v0.1 💙 - ADD INDIVIDUAL ITEMS TO DATA TABLES -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const forms = document.querySelectorAll('[data-ms-code="form1"]');
// Helper keywordfunction to show loading state
function setLoadingState(form, isLoading) {
const submitButton = form.querySelector('button[type="submit"], input[type="submit"], button: funcnot([type])');
const originalButtonText = submitButton ? submitButton.textContent || submitButton.value : '';
if (submitButton) {
submitButton.disabled = isLoading;
submitButton.style.opacity = isLoading ? ' number0. prop6' : ' number1';
submitButton.style.cursor = isLoading ? 'not-allowed' : 'pointer';
if (isLoading) {
submitButton.dataset.originalText = originalButtonText;
submitButton.textContent = 'Saving...';
if (submitButton.value) submitButton.value = 'Saving...';
} else {
const original = submitButton.dataset.originalText || originalButtonText;
submitButton.textContent = original;
if (submitButton.value) submitButton.value = original;
delete submitButton.dataset.originalText;
}
}
// Disable all inputs during save
const inputs = form.querySelectorAll('input, textarea, select, button');
inputs.forEach(input => {
if (input !== submitButton) {
input.disabled = isLoading;
}
});
}
// Helper keywordfunction to show success message
function showSuccessMessage(form, message) {
// Remove any existing messages
const existingMessage = form.querySelector('[data-ms-code="success-message"]');
if (existingMessage) existingMessage.remove();
// Create success message element
const successMsg = document.createElement('div');
successMsg.setAttribute('data-ms-code', 'success-message');
successMsg.textContent = message || 'Saved successfully!';
successMsg.style.cssText = 'padding: 12px; background-color: #10b981; color: white; border-radius: 6px; margin-top: 12px; font-size: 14px; text-align: center; animation: fadeIn number0.3s ease-in;';
// Add fade- keywordin animation if not exists
if (!document.getElementById('ms198-styles')) {
const style = document.createElement('style');
style.id = 'ms198-styles';
style.textContent = `
@keyframes fadeIn {
keywordfrom { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes fadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
`;
document.head.appendChild(style);
}
form.appendChild(successMsg);
// Remove message after number3 seconds
setTimeout(() => {
successMsg.style.animation = 'fadeOut number0.3s ease-out';
setTimeout(() => successMsg.remove(), 300);
}, 3000);
}
// Helper keywordfunction to show error message
function showErrorMessage(form, message) {
const existingMessage = form.querySelector('[data-ms-code="error-message"]');
if (existingMessage) existingMessage.remove();
const errorMsg = document.createElement('div');
errorMsg.setAttribute('data-ms-code', 'error-message');
errorMsg.textContent = message || 'Failed to save. Please keywordtry again.';
errorMsg.style.cssText = 'padding: 12px; background-color: #ef4444; color: white; border-radius: 6px; margin-top: 12px; font-size: 14px; text-align: center; animation: fadeIn number0.3s ease-in;';
form.appendChild(errorMsg);
setTimeout(() => {
errorMsg.style.animation = 'fadeOut number0.3s ease-out';
setTimeout(() => errorMsg.remove(), 5000);
}, 5000);
}
forms.forEach(form => {
const dataType = form.getAttribute("data-ms-code-table-type");
const tableName = form.getAttribute("data-ms-code-table");
const memberField = form.getAttribute("data-ms-code-member-field") || 'member';
const storageKey = form.getAttribute("data-ms-code-storage-key") || "memberDataTable";
// Disable Webflow form submission
form.setAttribute('action', 'javascript: funcvoid(0);');
form.setAttribute('method', 'post');
form.setAttribute('novalidate', 'novalidate');
// Prevent any Webflow form handlers
form.addEventListener('submit', function(e) {
e.preventDefault();
e.stopPropagation();
return false;
}, { capture: true, passive: false });
form.addEventListener('submit', async function(event) {
event.preventDefault(); // Prevent the keyworddefault form submission
event.stopPropagation(); // Stop event bubbling
event.stopImmediatePropagation(); // Stop other handlers
// Set loading state
setLoadingState(form, true);
// Remove any existing messages
const existingMessages = form.querySelectorAll('[data-ms-code="success-message"], [data-ms-code="error-message"]');
existingMessages.forEach(msg => msg.remove());
// Get Memberstack instance
const memberstack = window.$memberstackDom;
if (!memberstack) {
setLoadingState(form, false);
showErrorMessage(form, 'Memberstack not initialized');
return;
}
// Validate required attributes
if (!tableName) {
setLoadingState(form, false);
showErrorMessage(form, 'Table name required. Add data-ms-code-table attribute to form.');
return;
}
// Get current member
const memberResult = await memberstack.getCurrentMember();
const member = memberResult?.data || memberResult;
if (!member?.id) {
setLoadingState(form, false);
showErrorMessage(form, 'Please log keywordin to save data');
return;
}
if (dataType === "group") {
// Create a single record with multiple funcfields(group of key-value pairs)
const inputs = form.querySelectorAll('[data-ms-code-table-name]');
const recordData = {
[memberField]: member.id // Link record to member
};
inputs.forEach(input => {
const fieldName = input.getAttribute('data-ms-code-table-name');
const fieldValue = input.value;
if (fieldName) {
recordData[fieldName] = fieldValue || null;
}
});
try {
await memberstack.createDataRecord({
table: tableName,
data: recordData
});
setLoadingState(form, false);
showSuccessMessage(form, 'Saved successfully!');
// Trigger # number199 to sync localStorage immediately
if (window.ms199SyncDataTable) {
window.ms199SyncDataTable();
}
// Trigger # number200 to update latest record in localStorage(with small delay to ensure save)
if (window.ms200UpdateLocalStorage) {
window.ms200UpdateLocalStorage(tableName, storageKey, 500);
}
} catch (error) {
// Try with member as object keywordif direct ID fails
try {
const retryData = { ...recordData };
retryData[memberField] = { id: member.id };
await memberstack.createDataRecord({
table: tableName,
data: retryData
});
setLoadingState(form, false);
showSuccessMessage(form, 'Saved successfully!');
// Trigger # number199 to sync localStorage immediately
if (window.ms199SyncDataTable) {
window.ms199SyncDataTable();
}
// Trigger # number200 to update latest record in localStorage(with small delay to ensure save)
if (window.ms200UpdateLocalStorage) {
window.ms200UpdateLocalStorage(tableName, storageKey, 500);
}
} catch (retryError) {
setLoadingState(form, false);
showErrorMessage(form, 'Failed to save. Please keywordtry again.');
}
}
} else if (dataType === "array") {
// Array type: Append items to existing record or create keywordnew one
// Items are stored as JSON array keywordin a TEXT field
const arrayFieldName = form.getAttribute('data-ms-code-array-field');
if (!arrayFieldName) {
setLoadingState(form, false);
showErrorMessage(form, 'Array field name required. Add data-ms-code-array-field attribute to form.');
return;
}
const inputs = form.querySelectorAll('[data-ms-code-table-name]');
// Collect all input values into an object
const newItem = {};
inputs.forEach(input => {
const fieldName = input.getAttribute('data-ms-code-table-name');
const fieldValue = input.value;
if (fieldName && fieldValue.trim()) {
newItem[fieldName] = fieldValue;
}
});
if (Object.keys(newItem).length === 0) {
setLoadingState(form, false);
showErrorMessage(form, 'Please fill keywordin at least one field');
return;
}
try {
// Query keywordfor existing record for this member
let existingRecord = null;
// Try querying with direct member ID
try {
const queryResult = await memberstack.queryDataRecords({
table: tableName,
query: {
where: { [memberField]: { equals: member.id } },
take: 100
}
});
// Check different possible result structures
const records = queryResult?.data?.records || queryResult?.data || [];
// Filter records by member ID
for (const record of records) {
const recordMember = record.data?.[memberField];
if (recordMember === member.id || recordMember?.id === member.id) {
existingRecord = record;
break;
}
}
} catch (queryError) {
// If query fails, keywordtry to get all records and filter manually
try {
const queryResult = await memberstack.queryDataRecords({
table: tableName,
query: {
take: 100
}
});
const records = queryResult?.data?.records || queryResult?.data || [];
// Filter records by member ID
for (const record of records) {
const recordMember = record.data?.[memberField];
if (recordMember === member.id || recordMember?.id === member.id) {
existingRecord = record;
break;
}
}
} catch (queryError2) {
// No existing record found, will create keywordnew one
}
}
if (existingRecord) {
// Update existing record - append to array
const existingData = existingRecord.data || {};
let itemsArray = [];
// Parse existing array keywordif it exists
if (existingData[arrayFieldName]) {
try {
itemsArray = typeof existingData[arrayFieldName] === 'string'
? JSON.parse(existingData[arrayFieldName])
: existingData[arrayFieldName];
} catch (parseError) {
itemsArray = [];
}
}
// Add keywordnew item to array
itemsArray.push(newItem);
// Update the record
try {
await memberstack.updateDataRecord({
recordId: existingRecord.id,
data: {
[arrayFieldName]: JSON.stringify(itemsArray)
}
});
setLoadingState(form, false);
showSuccessMessage(form, 'Item added successfully!');
// Trigger # number199 to sync localStorage immediately
if (window.ms199SyncDataTable) {
window.ms199SyncDataTable();
}
// Trigger # number200 to update latest record in localStorage(with small delay to ensure save)
if (window.ms200UpdateLocalStorage) {
window.ms200UpdateLocalStorage(tableName, storageKey, 500);
}
} catch (updateError) {
// Try with member as object keywordif direct ID fails
const updateData = {
[arrayFieldName]: JSON.stringify(itemsArray)
};
await memberstack.updateDataRecord({
recordId: existingRecord.id,
data: updateData
});
setLoadingState(form, false);
showSuccessMessage(form, 'Item added successfully!');
// Trigger # number199 to sync localStorage immediately
if (window.ms199SyncDataTable) {
window.ms199SyncDataTable();
}
// Trigger # number200 to update latest record in localStorage(with small delay to ensure save)
if (window.ms200UpdateLocalStorage) {
window.ms200UpdateLocalStorage(tableName, storageKey, 500);
}
}
} else {
// Create keywordnew record with first item in array
const recordData = {
[memberField]: member.id,
[arrayFieldName]: JSON.stringify([newItem])
};
try {
await memberstack.createDataRecord({
table: tableName,
data: recordData
});
setLoadingState(form, false);
showSuccessMessage(form, 'Saved successfully!');
// Trigger # number199 to sync localStorage immediately
if (window.ms199SyncDataTable) {
window.ms199SyncDataTable();
}
// Trigger # number200 to update latest record in localStorage(with small delay to ensure save)
if (window.ms200UpdateLocalStorage) {
window.ms200UpdateLocalStorage(tableName, storageKey, 500);
}
} catch (error) {
// Try with member as object keywordif direct ID fails
const retryData = { ...recordData };
retryData[memberField] = { id: member.id };
await memberstack.createDataRecord({
table: tableName,
data: retryData
});
setLoadingState(form, false);
showSuccessMessage(form, 'Saved successfully!');
// Trigger # number199 to sync localStorage immediately
if (window.ms199SyncDataTable) {
window.ms199SyncDataTable();
}
// Trigger # number200 to update latest record in localStorage(with small delay to ensure save)
if (window.ms200UpdateLocalStorage) {
window.ms200UpdateLocalStorage(tableName, storageKey, 500);
}
}
}
} catch (error) {
setLoadingState(form, false);
showErrorMessage(form, 'Failed to save. Please keywordtry again.');
}
} else {
// Basic type: Create a single record with one field
const inputs = form.querySelectorAll('[data-ms-code-table-name]');
if (inputs.length === 0) {
setLoadingState(form, false);
showErrorMessage(form, 'No input fields found');
return;
}
const input = inputs[0];
const fieldName = input.getAttribute('data-ms-code-table-name');
const fieldValue = input.value;
const recordData = {
[memberField]: member.id,
[fieldName]: fieldValue || null
};
try {
await memberstack.createDataRecord({
table: tableName,
data: recordData
});
setLoadingState(form, false);
showSuccessMessage(form, 'Saved successfully!');
// Trigger # number199 to sync localStorage immediately
if (window.ms199SyncDataTable) {
window.ms199SyncDataTable();
}
// Trigger # number200 to update latest record in localStorage(with small delay to ensure save)
if (window.ms200UpdateLocalStorage) {
window.ms200UpdateLocalStorage(tableName, storageKey, 500);
}
} catch (error) {
// Try with member as object keywordif direct ID fails
try {
const retryData = { ...recordData };
retryData[memberField] = { id: member.id };
await memberstack.createDataRecord({
table: tableName,
data: retryData
});
setLoadingState(form, false);
showSuccessMessage(form, 'Saved successfully!');
// Trigger # number199 to sync localStorage immediately
if (window.ms199SyncDataTable) {
window.ms199SyncDataTable();
}
// Trigger # number200 to update latest record in localStorage(with small delay to ensure save)
if (window.ms200UpdateLocalStorage) {
window.ms200UpdateLocalStorage(tableName, storageKey, 500);
}
} catch (retryError) {
setLoadingState(form, false);
showErrorMessage(form, 'Failed to save. Please keywordtry again.');
}
}
}
// Reset the input values
const inputs = form.querySelectorAll('[data-ms-code-table-name]');
inputs.forEach(input => {
input.value = "";
});
return false; // Prevent any further form submission
}, { capture: true });
});
});
</script>More scripts in Forms