Display a waiting screen for the exact duration of a long automation / Make scenario with ChatGPT or else

Hi all!

Currently, all of the snippets to generate a waiting screen while the data is being processed in make or in an Airtable automation relied on a static duration (setTimeout).

The following code allows to display a waiting screen with a dynamic duration. It means that the waiting screen will only last the exact time of the data process in Make or elsewhere.

Few words about my current setup (you can check it in the Gif below, to be more visual).

  • A list details block to catch the right recordId
  • A form block to send the record of the list-details block to Make.
  • A list block gathering different records including the one in the list-details block

ezgif-1-90c8d68d87

The code relies on a mutation observer. Another version might be released in the next few weeks so the code relies on a data flag.

Here is the full code, to be inserted in the custom code header of the page settings:

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">

<style>
  #loading-screen {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: #4893eb;
    z-index: 9999;
  }

  #loading-screen .loading-content {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    color: #fff;
    text-align: center;
    font-size: 20px;
    font-weight: bold;
  }

  #loading-screen .loading-spinner {
    font-size: 40px;
    margin-top: 20px;
  }
</style>

<script>
window.addEventListener('block-loaded-list2', () => {
    console.log('Block loaded');
});

const showLoadingScreen = () => {
    document.getElementById('loading-screen').style.display = 'block';
};

const hideLoadingScreen = () => {
    document.getElementById('loading-screen').style.display = 'none';
};

const dispatchReloadBlockListDetailsEvent = () => {
    window.dispatchEvent(new CustomEvent('reload-block-list-details1'));
};

let observer;
let reloadInterval;

window.addEventListener('submit-form-success-form1', () => {
    showLoadingScreen();

    const targetElements = document.querySelectorAll('#list2 h3');

    const config = { childList: true };

    const callback = function (mutationsList, observer) {
        for (const mutation of mutationsList) {
            if (mutation.type === 'childList') {
                console.log('Child elements have changed');
                hideLoadingScreen();
                dispatchReloadBlockListDetailsEvent();

                clearInterval(reloadInterval);

                observer.disconnect();
            }
        }
    };

    targetElements.forEach(targetElement => {
        observer = new MutationObserver(callback);
        observer.observe(targetElement, config);
    });

    reloadInterval = setInterval(() => {
        window.dispatchEvent(new CustomEvent('reload-block-list2'));
    }, 4000);
});

const onRecords = (e) => {
    console.log(e.detail);
};

</script>

Place this in the footer code of the page settings:

<div id="loading-screen">
  <div class="loading-content">
    <p>Your xxxxx is being created. Patience...</p>
    <i class="fas fa-spinner fa-spin loading-spinner"></i>
  </div>
</div>

How to adapt the script to your use case?

  • You can customize the waiting screen as you wish, obviously.
  • update submit-form-success-form1 by changing form1 to the ID:Name of your form block.
  • Same for reload-block-list2 and reload-block-list-details1
  • You will absolutely need to adapt this line: const targetElements = document.querySelectorAll('#list2 h3');

#list2 h3 is the selector checked by the mutation observer (targetElement)
How did I get this precise selector? It’s simple: open the dev tool/inspector of your browser and point to the element that should change when the data is finally updated. In my use case I chose the text element for the title of the list block items (Project A, Project B, Project C, etc.).
Once you selected it in your dev tool, just check what is the type of the selector. Here in my use case it’s a h3. You don’t need to use the class (unless you need your mutation observer to point to a more precise selector).

I insert a screenshot of my dev tool (in french but you will get it) below.

Screenshot of my browser dev tool:

1 Like

Another version of the code if you want to redirect the user to another page instead of staying on the same page.

Don’t forget to change =>

  1. This line: window.location.href = '/hide-show-tabs'; /hide-show-tabs to be replaced by /xxxx (the page url slug you want the user to be redirected to)
  2. #list2 h3 from const targetElements = document.querySelectorAll('#list2 h3');
  3. form1 from window.addEventListener('submit-form-success-form1', () => {
  4. list2 from window.addEventListener('block-loaded-list2', () => {
  5. list2 from window.dispatchEvent(new CustomEvent('reload-block-list2'));

In the header custom code:

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">

<style>
  #loading-screen {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: #4893eb;
    z-index: 9999;
  }

  #loading-screen .loading-content {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    color: #fff;
    text-align: center;
    font-size: 20px;
    font-weight: bold;
  }

  #loading-screen .loading-spinner {
    font-size: 40px;
    margin-top: 20px;
  }
</style>

<script>
window.addEventListener('block-loaded-list2', () => {
    console.log('Block loaded');
});

const showLoadingScreen = () => {
    document.getElementById('loading-screen').style.display = 'block';
};

const hideLoadingScreen = () => {
    document.getElementById('loading-screen').style.display = 'none';
};

const redirectToPage = () => {
    window.location.href = '/hide-show-tabs';
};

window.addEventListener('submit-form-success-form1', () => {
    showLoadingScreen();

    const targetElements = document.querySelectorAll('#list2 h3');

    const config = { childList: true };

    const callback = function (mutationsList, observer) {
        for (const mutation of mutationsList) {
            if (mutation.type === 'childList') {
                console.log('Child elements have changed');
                hideLoadingScreen();
                redirectToPage();
            }
        }
    };

    targetElements.forEach(targetElement => {
        const observer = new MutationObserver(callback);
        observer.observe(targetElement, config);
    });

    const reloadInterval = setInterval(() => {
        window.dispatchEvent(new CustomEvent('reload-block-list2'));
    }, 4000);
});

const onRecords = (e) => {
    console.log(e.detail);
};

</script>

In the footer custom code:

<div id="loading-screen">
  <div class="loading-content">
    <p>Your xxxxx is being created. Patience...</p>
    <i class="fas fa-spinner fa-spin loading-spinner"></i>
  </div>
</div>
2 Likes

Hi Matthieu,

I have seen different posts from you where you provide custom code for different waiting screens. From the responses to these posts I understand that your code should work, but somehow I only manage to successfully implement the reload triggers with time delay. But no matter what I try, I can’t get the waiting screen to show up
 do you have any idea what may be the cause of the waiting screen not showing up?

Note that I already tried with the browser’s popup blocker disabled. Also, I did manage to do something else: I had already copied the code for a generic loading screen into the website’s general custom code (if I’m not mistaken you also provided that code). And when I call upon that screen (instead of the code in this post for example), it DOES show up (but without any text specific to the trigger on why the screen is showing, plus the spinner is suddenly presented on top-left, while the custom code says center/center).

Best regards,
Thijs

Hi,

Just copy pasting the code won’t work.

For example, this line => const targetElements = document.querySelectorAll('#list2 h3');

This line needs to be udpated according to your use case, as I wrote above. The script relies on a mutation observer, the target element for your use case might not be the same.

If this is another problem, it can come from 
 anywhere :woozy_face: (but not from browser’s popup blocker as the code is not related to popups).

Hi Matthieu,

Yes I understand the triggers to (de)activate the loading screen have to be tailored. And I succeeded in the one that has a fixed time-out. My problem is that despite the code saying it should activate
 it doesn’t show (but it simply reloads the blocks as specified in the code).

Thanks for you response!

All codes work properly, that’s all I can say.

I can debug if you show me your complete code (the dynamic timeout one, the one I wrote in this page). and if you explain the complete workflow of your page (with block IDs too).

Hi,

I updated the codes for a better understanding

  • Easier selector to find
  • More dynamic to get any change in the listed records of the dynamic block (added .forEach)

Hi Matthieu

This is the exact use-case I have been struggling for days with! I have a different implementation to you so I can’t easily adapt.

Where I am up to is that the observer shows me the record when I submit or when I update but then the loop keeps going but it never detects any changes in that record.

I can get the console to show me when new changes are detected in the Block Table but the observer only shows me the new innerText if I change something in a record not currently shown on the screen. i.e. if my table shows 5 rows, only if I make a change in airtable to a record 6 and above will the console show me it has new text and check for the text ‘Processing complete’.

I have a processing status in airtable which is a Tag in the table (ideas-table) and that is what I wanted to check for the change. Have I over engineered this or is there a solution?

Any help would be hugely appreciated as this is driving me crazy :sweat_smile:

From my page footer:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Interactive Page with Ideas Table</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
    <style>
        #loading-screen {
            display: none;
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            //background-color: #4893eb;
            z-index: 9999;
        }

        #loading-screen .loading-content {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            color: #fff;
            text-align: center;
            font-size: 20px;
            font-weight: bold;
        }

        #loading-screen .loading-spinner {
            font-size: 40px;
            margin-top: 20px;
        }
    </style>
</head>
<body>

<div id="loading-screen">
    <div class="loading-content">
        <p>Your xxxxx is being created. Patience...</p>
        <i class="fas fa-spinner fa-spin loading-spinner"></i>
    </div>
</div>

<div id="ideas-table">
    <!-- Dynamic content will be loaded here -->
</div>

<script>
    const showLoadingScreen = () => {
        console.log("Attempting to show loading screen.");
        const modal = document.getElementById('loading-screen');
        modal.style.display = 'block';
        console.log("Modal display status after showing:", modal.style.display);
    };

    const hideLoadingScreen = () => {
        console.log("Attempting to hide loading screen.");
        const modal = document.getElementById('loading-screen');
        console.log("Modal display status before hiding:", modal.style.display);
        modal.style.display = 'none';
        console.log("Modal display status after hiding:", modal.style.display);
    };

window.addEventListener('submit-form-success-ideas-table', () => {
    showLoadingScreen();
    console.log("Processing started...");

    let targetTable = document.getElementById('ideas-table');
    const observer = new MutationObserver((mutationsList) => {
        for (const mutation of mutationsList) {
            if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                mutation.addedNodes.forEach((node) => {
                    if(node.nodeType === Node.ELEMENT_NODE ) {
                        const rowIndex = node.getAttribute('row-index');
                            if (rowIndex === "0") {
                                let dataContent = node.innerText;
                                console.log("Row with row-index 0 found: " + dataContent);
                                if (dataContent === "Processing Complete") {
                                    console.log('New "Processing Complete" detected in a new row');
                                    hideLoadingScreen();
                                    observer.disconnect(); // Optional: Disconnect if no further monitoring is needed
                                    clearInterval(reloadInterval);
                                } else {
                                    targetTable = document.getElementById('ideas-table');
                                }
                            }
                        
                    }
                    
                });
                
            }
        }
    });
    observer.observe(targetTable, { childList: true, subtree: true });
    
    reloadInterval = setInterval(() => {
        window.dispatchEvent(new CustomEvent('reload-block-ideas-table'));
    }, 4000);
    
});
    
window.addEventListener('update-record-success-ideas-table', function() {
    showLoadingScreen();
    console.log("Processing started...");
 
    let targetTable = document.getElementById('ideas-table');
    const observer = new MutationObserver((mutationsList) => {
        for (const mutation of mutationsList) {
            if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                mutation.addedNodes.forEach((node) => {
                    if(node.nodeType === Node.ELEMENT_NODE) {
                        const rowIndex = node.getAttribute('row-index');
                        if (rowIndex === "0") {    
                            let dataContent = node.innerText;
                            console.log("Row with row-index 0 found: " + dataContent);
                            if (dataContent.includes("Processing Complete")) {
                                console.log('New "Processing Complete" detected in a new row');
                                hideLoadingScreen();
                                observer.disconnect(); // Optional: Disconnect if no further monitoring is needed
                                clearInterval(reloadInterval);
                            } 
                        }
                    }
                    
                });
                
            }
        }
    });
    observer.observe(targetTable, { childList: true, subtree: true });
    
    reloadInterval = setInterval(() => {
        window.dispatchEvent(new CustomEvent('reload-block-ideas-table'));
    }, 4000);
    
});

    
    
</script>
</body>
</html>

Screenshot 2024-02-18 at 17.39.39

Sorry can’t embed more than one thing in a message as a new user.

Hey Matthieu and adamnagus. I’m guessing that softr might have implemented some changes since this project was originally posted in Nov. 23. I tried using the same code as first posted by Matthieu, and it works great, except the mutation observer doesn’t seem to be recognizing changes made to the targetElements. I can see the list block is getting reloaded with my targetElements (<h3 class elements in my list block) with each dispatchEvent. However, when I make changes to those elements in Airtable, those changes are reflected on the website and elements inspector, but are not recognized by the mutation observer. If I console.log(targetElements) with each dispatchEvent I can see that the observer is not recognizing any changes to the targetElements (It will repeatedly give the same old <h3 text elements as when the page was loaded, even if those elements have been changed or deleted and are no longer visible on the webpage or page inspector). FYI, I can get the mutation observer to trigger if I set the target to ‘#list2’ instead of ‘#list2 h3.’ However, it triggers immediately and the console says that the trigger has resulted from the mutation of an added node associated with “The record has been updated successfully” response. Hope that context might be helpful to what you are working on.

Hi,

Yes, there were some changes as it doesn’t work as well as it used to be.
I need to do some changes to the code and talk with the engineering team.

Will keep you posted when it’s updated.

1 Like

Sounds good matthieu. Thanks for any info!

UPDATE

Creating a new record in Airtable through a long Make automation (imagine ChatGPT or any other use case), displaying a waiting screen while the automation is performed and finally update a list below the form (to show the newly created record) or redirect to another page.

Another way to do this is to rely on webhook response from Make.

The page setup of my example is:

  • A form that creates a project record in Airtable through Make
  • A list block displaying all project

1) Reload a block

In this page add this custom code in the footer part:
In the script at the end => form1 and list1 to be replaced by your block IDs

<div id="loading-screen">

    <div class="loading-content">

    <p>Updating Data... Please Wait...</p>

    <i class="fas fa-spinner fa-pulse loading-spinner"></i>

    </div>

 </div>

<style>

    #loading-screen {

      display: none;

      position: fixed;

      top: 0;

      left: 0;

      width: 100%;

      height: 100%;

      background-color: #012f6d;

      z-index: 9999;

    }

    #loading-screen .loading-content {

      position: absolute;

      top: 50%;

      left: 50%;

      transform: translate(-50%, -50%);

      color: #ffffff;

      text-align: center;

      font-size: 20px;

      font-weight: bold;

    }

    #loading-screen .loading-spinner {

      font-size: 40px;

      margin-top: 20px;

    }


</style>


<script>
    window.addEventListener('submit-form-success-form1', (e) => {
        const response = e.detail.response || {};
        const data = response.data;
        
        if (data && data.reload) {
            window.dispatchEvent(new CustomEvent('reload-block-list1'));

            const loadingScreen = document.querySelector('#loading-screen');
            if (loadingScreen) {
                loadingScreen.style.display = 'none';
            }
        }
    });
</script>


Add this in the header csutom code:

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">

<script>
    document.addEventListener('DOMContentLoaded', () => {
        const formButton = document.querySelector('#form1 .MuiButtonBase-root');

        formButton.addEventListener('click', function() {
            document.getElementById('loading-screen').style.display = 'block';
        });
    });
</script>

Make setup

The first module is a webhook receiving data from the form

The second module creates a record in Airtable with the webhook data

The third module is a sleep module that imitates a 15 second scenario - in your use case it could be anything, like a chatGPT module.

The fourth module is a webhook response
code status: 200
body: { "reload": true }

Here is the result:

ezgif-3-99a0e7bf1f

1 Like

2) Redirect to another page

Change the script in the footer by

<script>
    window.addEventListener('submit-form-success-form1', (e) => {
        const response = e.detail.response || {};
        const data = response.data;
        
        if (data && data.redirectUrl) {
            window.location.href = data.redirectUrl;
        }
    });
</script>

Change the Make webhook response (last module) by:

{
  "redirectUrl": "https://yourdomain/the-page-to-redirect-to"
}
1 Like

Hey there! I assume all of this can be replicated with Zapier?

It might be possible if Zapier can handle custom webhook responses (This is why Make is used as I’m sure it can handle custom webhook responses)