#198 - Add Individual Items to Data Tables

Let members save simple values, grouped fields, or growing lists into Data Tables.

Video Tutorial

tutorial.mov

Watch the video for step-by-step implementation instructions

The Code

450 lines
Paste this into Webflow
<!-- 💙 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>

Script Info

Versionv0.1
PublishedDec 12, 2025
Last UpdatedDec 9, 2025

Need Help?

Join our Slack community for support, questions, and script requests.

Join Slack Community
Back to All Scripts

Related Scripts

More scripts in Forms