DEV Community

Cover image for Web browsers: confirm leaving page when there is modified data.
Be Hai Nguyen
Be Hai Nguyen

Posted on

Web browsers: confirm leaving page when there is modified data.

Web sites such as Facebook implements a confirmation to leave the current page when there is unsaved data. This is not at all hard to do, but it did take me some experimentations to find out what works. I'm discussing my findings in this post.

I'm using the Jinja templating engine to generate HTML pages. For client-side, I'm using JQuery. I'm mentioning the use of the Jinja templating engine is for the shake of completeness, whatever methods employed to generate dynamic HTML are irrelevant, since what we're discussing is purely client-side.

To implement this leave page confirmation, we need to handle the Window: beforeunload event. I'd like to do it for all pages across the site, so I implement it in the base template:

File base_template.html
Enter fullscreen mode Exit fullscreen mode
<!doctype html>
<html lang="en" class="h-100">
<head>
    ...
    <script src="http://localhost/work/jquery/js/jquery-3.6.0.min.js"></script>
    ...

    <script>
        // Individual pages must set and reset this during their operations.    
        let dataChanged = false;

        const setDataChanged = () => dataChanged = true;
        const resetDataChanged = () => dataChanged = false;

        $( document ).ready( function(event) {

            $( window ).on( 'beforeunload', function( event ) {
                if ( dataChanged ) 
                    // return confirm( 'There are unsaved data. Please confirm.' );
                    // return false;
                    // return true;
                    return null;
            });

        });
    </script>
</head>

<body class="d-flex flex-column h-100">
    {% block content %}
    <!-- Content of specific individual pages go here. -->
    {% endblock %}
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

The only thing โ€œJinjaโ€ about this page is the following block:

    {% block content %}
    <!-- Content of specific individual pages go here. -->
    {% endblock %}
Enter fullscreen mode Exit fullscreen mode

Basically, at runtime the processing engine replaces this block with a requested page to make a complete and valid HTML page to return to the requesting client.

The codes for the beforeunload event is simple: if variable dataChanged is true, then do a return null, otherwise nothing.

I have three ( 3 ) return statements commented out: they have the same effect as return null.

It's the responsibility of the pages to set and reset dataChanged by calling setDataChanged() and resetDataChanged() respectively. Please note that when the page is first loaded, dataChanged is initialised to false.

The general structure of individual pages that go into base_template.html's block content:

File a_specific_page.html
Enter fullscreen mode Exit fullscreen mode
{% extends "page_template.html" %}

{% block content %}
<script>
    function bindDataChange() {
        $( '.selector-input, input[type="checkbox"]' ).on( 'change', function(event) {
            ...
            setDataChanged();

            event.preventDefault();
        });
    };

    function bindSave() {
        function savedSuccessful( data ) {
            resetDataChanged();
            ...
        };

        $( '#saveBtn' ).on( 'click', function( even ) {

            if ( !$('#somethingFrm').parsley().validate() ) return false;

            // Send data to server using AJAX. Response is JSON status.
            saveData( '/save-something/', '{{ csrf_token() }}',
                $( '#somethingFrm' ).serialize(), savedSuccessful );

            event.preventDefault();
        });
    };

    $( document ).ready( function() {
        ...
        bindDataChange();
        bindSave();
        ...
    });
</script>

<div class="d-flex row justify-content-center h-100">
    ...
</div>
{% endblock %}
Enter fullscreen mode Exit fullscreen mode

Note where setDataChanged() and resetDataChanged() get called. Please also recall that when the page is first loaded, dataChanged is initialised to false.

This a_specific_page.html saves data via AJAX call, the response is a JSON status, which indicates the outcome of the request: the page does not get reloaded during this operation.

In short, whenever users do anything on the screen which requires data to be persisted, setDataChanged() must be called. And after data has been successfully saved, resetDataChanged() must get called.

What about form submission?

Form submission means leaving the current page, the beforeunload event could potentially be triggered. The page segment below shows how I solve this issue:

File a_form_submit_page.html
Enter fullscreen mode Exit fullscreen mode
{% extends "base_form_template.html" %}

{% block content %}
<script>
    function bindDataChange() {
        $( 'selectors' ).on( 'change', function( event ) {
            ...
            setDataChanged();
        });
    };

    function bindUpdateBtn() {
        $( '#updateBtn' ).on( 'click', (event) => {
            form = $( '#someFrm' );
            ...
            //Don't trigger page leaving event.
            resetDataChanged();

            form.submit()

            event.preventDefault();
        });
    };

    $(document).ready( () => {
        bindDataChange();
        bindUpdateBtn();
    }); 
</script>

<div class="d-flex">
    <form method="POST" action="{{ request.path }}" id="someFrm">
        {{ form.csrf_token }}
        ...
        <button type="submit" id="updateBtn" class="btn btn-primary btn-sm" disabled="disabled">Update</button>
        ...
    </form>
</div>

{% endblock %}
Enter fullscreen mode Exit fullscreen mode

I must submit forms manually, in this case via function bindUpdateBtn(). This gives me a chance to call resetDataChanged() -- even though the data has not been saved -- before unloading the page so the event will not be triggered.

If you have noticed, each page has two ( 2 ) $(document).ready() declarations, browsers seem to allow this, so far I don't have any complaint. But I am not sure if this is a good implementation?

I have tested this approach with the following browsers:

  • FireFox -- Version: 106.0.3 (64-bit)
  • Opera -- Version: 92.0.4561.21, System: Windows 10 64-bit
  • Chrome -- Version 106.0.5249.119 (Official Build) (64-bit)
  • Edge -- Version 106.0.1370.52 (Official build) (64-bit)


I've also tested it with browsers' tab closing, and it also works.

I'm not at all sure if this is the best implementation or not, but it seems to work for me. If problems arise later on, I'll do an update on it. Thank you for reading. I do hope you find this post useful. Stay safe as always.

Top comments (0)