Multi-step user account editing page

Hey!

I’ve developed a solution for a common challenge in Softr: creating a multi-step form for editing user accounts. This is particularly useful when dealing with multiple fields, as a single long form might overwhelm users.

The Challenge

Softr doesn’t have a built-in option for multi-step forms when editing user profiles, making it challenging for users to fill out all fields at once.

The Solution

To overcome this limitation, I created a workaround using four separate “User Profile” blocks. Here’s how it works:

  1. Split your form fields into four blocks:
  • user-accounts1
  • user-accounts2
  • user-accounts3
  • user-accounts4
  1. Use custom code to navigate between these blocks, creating a multi-step form experience.

Implementation

1. HTML (Add to the Custom Code block in the Header section):

html

<div class="area-editing-steps">
    <span class="editing-steps">Step 1</span>
    <a class="editing-steps-previous">« Previous</a>
</div>

2. CSS (Add to the Custom Code block in the Header section):

css

<style>
.area-editing-steps {
    display: flex;
    align-items: center;
    justify-content: center;
    position: relative;
    width: 33%;
    margin: 0 auto;
}

.editing-steps {
    color: #9796FC;
    font-size: 26px;
    font-weight: bold;
}

.editing-steps-previous {
    color: #9796FC;
    font-size: 26px;
    margin-left: 10px;
    cursor: pointer;
    transition: color 0.3s ease, transform 0.3s ease;
}

.editing-steps-previous:hover {
    color: #6e6cfb;
    transform: scale(1.1);
}

.editing-steps-previous:active {
    color: #4d4ccb;
    transform: scale(0.9);
}

#user-accounts1, #user-accounts2, #user-accounts3, #user-accounts4 {
    display: none;
}

#user-accounts1.active, #user-accounts2.active, #user-accounts3.active, #user-accounts4.active {
    display: block;
}
</style>

3. JavaScript (Add to the Custom Code block in the Footer section):

javascript

document.addEventListener('DOMContentLoaded', () => {
    let currentStep = 1; // Start at step 1
    const maxSteps = 4;  // Total number of steps

    // Update step text and visibility
    const updateStepText = () => {
        document.querySelector('.editing-steps').textContent = `Step ${currentStep}`;
        updateVisibility();
    };

    // Show relevant step and hide others
    const updateVisibility = () => {
        for (let i = 1; i <= maxSteps; i++) {
            const section = document.querySelector(`#user-accounts${i}`);
            if (section) {
                section.classList.toggle('active', i === currentStep);  // Toggle visibility based on current step
            }
        }
    };

    // Add event listener to the "Next" button only
    const addClickListenerToNextButton = () => {
        const nextButton = document.querySelector('.MuiButton-containedPrimary');  // Select the "Next" button by class
        if (nextButton) {
            nextButton.removeEventListener('click', handleButtonClick);  // Remove previous listeners to avoid duplication
            nextButton.addEventListener('click', handleButtonClick);     // Add new listener for the "Next" button
        }
    };

    // Handle "Next" button click
    const handleButtonClick = (event) => {
        const button = event.currentTarget;
        const parentForm = button.closest('form');
        if (parentForm) {
            setTimeout(() => {
                if (!checkForErrors(parentForm)) {  // If no errors, proceed to the next step
                    if (currentStep < maxSteps) {
                        currentStep++;  // Move to the next step
                        updateStepText();  // Update text and visibility for the new step
                    }
                } else {
                    console.debug('Error detected, staying on the same step');  // If error found, log and stay on the current step
                }
            }, 500);  // Wait 500ms for any error to appear
        }
    };

    // Check for form errors
    const checkForErrors = (parentForm) => {
        const errorElement = parentForm.querySelector('.form-error-text');  // Look for error messages
        return errorElement !== null;  // Return true if errors are present
    };

    // "Previous" button listener to go back a step
    document.querySelector('.area-editing-steps').addEventListener('click', (event) => {
        if (event.target.classList.contains('editing-steps-previous')) {
            if (currentStep > 1) {  // Only allow moving back if not on the first step
                currentStep--;  // Go back one step
                updateStepText();  // Update text and visibility for the previous step
            }
        }
    });

    // Initialize the step display and text on page load
    updateStepText();

    // Observer for dynamically added "Next" buttons
    const observer = new MutationObserver(addClickListenerToNextButton);
    observer.observe(document.body, { childList: true, subtree: true });  // Listen for changes in the DOM to dynamically add listeners

    // Add event listener to the "Next" button present on page load
    addClickListenerToNextButton();
});

How It Works

  1. HTML creates a navigation area displaying the current step and a “Previous” button.
  2. CSS styles the navigation and manages step visibility.
  3. JavaScript handles step logic, checks for form errors, and moves to the next step only if no errors are present.

Feel free to adapt the code to your needs. If you have any questions or suggestions, let me know. Happy coding!

1 Like

Additional Step (Required)

You must add the following JavaScript to the Custom Code block in the Footer section of your Softr page to remove the first two default fields (name and email) from all forms except the first one:

javascript

document.addEventListener("DOMContentLoaded", function() {
    // Function to hide the first two elements in each of the elements with the classes user-accounts2, user-accounts3, and user-accounts4
    function hideFirstTwoElements() {
        // List of container IDs where the first two fields should be hidden
        const containers = ['user-accounts2', 'user-accounts3', 'user-accounts4'];

        containers.forEach(function(containerId) {
            // Select the element with the current ID
            const container = document.getElementById(containerId);

            if (container) { // Check if the element exists
                // Select the first two children with the class 'form-input-holder' inside the current element
                const inputHolders = container.querySelectorAll('.form-input-holder');
                for (let i = 0; i < 2; i++) { // Loop through the first two elements
                    if (inputHolders[i]) { // Check if the element exists
                        inputHolders[i].style.display = 'none'; // Hide the element by setting 'display' to 'none'
                    }
                }
            }
        });
    }

    // Set up a MutationObserver to watch for changes in the DOM
    const observer = new MutationObserver(function(mutations) {
        mutations.forEach(function(mutation) {
            if (mutation.addedNodes.length > 0) { // If new elements are added
                hideFirstTwoElements(); // Call the function to hide elements
            }
        });
    });

    // Start observing the entire document body, including all sub-elements
    observer.observe(document.body, { childList: true, subtree: true });
});

This code is crucial to ensure that the first two fields (name and email) are only visible in the first form and hidden in subsequent forms (user-accounts2, user-accounts3, and user-accounts4). Ensure you add this code to your Softr page’s Footer section for proper functionality.

Add this code to make sure your multi-step form behaves as expected. If you need further assistance or have any questions, feel free to reach out. Happy coding!

2 Likes