Search code examples
javascriptjquerydialogsettimeoutcleartimeout

clearTimeout doesn't clear the timeout variable


I have an application that needs to be timed out after 28 minutes. It will show the jQuery dialog for 2 minutes. If the user clicks "Ok" within the two minutes the timer refreshes to 28 minutes and when the count down reaches 0 minutes it should show the jQuery dialog again.

The issue is when I debug, I see the timeout variable is not clearing out the timeout. After I click "Ok", the timer resets to 28 minutes but the setTimeout doesn't show the dialog again when the count down reaches 0 minutes.

Here is my code:

var timeout;
function timer() {
countDownDate = 0;
    console.log("Hello");
    countDownDate = 0;
    var timeExpires = new Date().getTime();
    countDownDate = timeExpires + 1680000;
    now = 0;
    distance = 0;
    if(timeout){
    clearTimeout(timeout);
    }
    // Update the count down every 1 second
    var x = setInterval(function () {

        // Get todays date and time
         now = new Date().getTime();

        // Find the distance between now an the count down date
         distance = countDownDate - now;

        // Time calculations for days, hours, minutes and seconds
        var days = Math.floor(distance / (1000 * 60 * 60 * 24));
        var hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
        var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
        var seconds = Math.floor((distance % (1000 * 60)) / 1000);

        if (distance < 0) {
        if ($("#alert").length) {
            var title;
            if ($("#alert span").length) {
                title = $("#alert span").text();
            }
            $("#alert div").dialog({
                title: title,
                modal: true,
                buttons: {
                    Ok: function() {
                        var foo = $(this);
                        clearTimeout(timeout);
                        timer();
                        foo.dialog('close');
                    },
                    Cancel: function() {
                      esriId.destroyCredentials();
                      window.location.replace(redirect_uri);
                    }
                  }/*,
                open: function() {
                    var foo = $(this);
                    timeout = setTimeout(function() {
                        foo.dialog('close');
                        esriId.destroyCredentials();
                        window.location.replace(redirect_uri);
                    }, 120000);
                },*/

            });
            }
            timeout = setTimeout($("#alert div").dialog("open"), 120000);
            clearInterval(x);
        }
    }, 1000);
};

Here is the HTML div for the alert:

<div id="alert" style="display:none">
            <span>You will be signed out</span>
            <div>You will be signed out due to inactivity on the page. If you wish to stay on the page, please press 'Ok'</div>
        </div>

Solution

  • As written,

    • setTimeout() command is wrongly constructed.
    • you appear to want to open the dialog when the 28 minutes expire and close it automatically after a further 2 minutes if OK is not clicked; but you are using that 2 minute timer to open the dialog, not to close it.
    • it's difficult to see the wood for the trees inside timer().

    timer() can be simplified by :

    • defining the dialog setup in a function, outside timer().
    • defining the countdown timer calcs/display in a function, outside timer().
    • introducing a third timer to manage the 28 minute duration - this avoids the need to test if (distance < 0) etc in the time display function.

    Here's the revised code :

    // set up the dialog
    function showDialog(callback) {
        $("#alert div").dialog('destroy').dialog({
            title: $("#alert span").length ? $("#alert span").text() : '---',
            modal: true,
            buttons: {
                Ok: function() {
                    $(this).dialog('close');
                    callback('ok')
                },
                Cancel: function() {
                    // $(this).dialog('close'); // ?
                    callback('cancel');
                }
            }
        }).dialog('open');
    }
    
    // function for calculating and displayig the countdown values
    function displayCountdown(t) {
        var distance = Math.max(0, t - Date.now());
        var days = Math.floor(distance / (1000 * 60 * 60 * 24));
        var hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
        var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
        var seconds = Math.floor((distance % (1000 * 60)) / 1000);
        // ... display days, hours, minutes, seconds somewhere
    }
    
    // object which defines the three timers
    var timers = {
        'ticker':     { 'ref': null, 't': 1000 }, // 1 second
        'stopTicker': { 'ref': null, 't': 1680000}, // 28 minutes
        'cancelDialog': { 'ref': null, 't': 120000 } // 2 minutes
    }
    
    function redirect() {
        esriId.destroyCredentials();
        window.location.replace(redirect_uri);
    }
    
    // 
    function timer() {
        clearInterval(timers.ticker.ref);
        clearTimeout(timers.stopTicker.ref);
        clearTimeout(timers.cancelDialog.ref);
        timers.stopTicker.ref = setTimeout(function() {
            clearInterval(timers.ticker.ref);
            showDialog(function(outcome) {
                if(outcome === 'ok') {
                    timer();
                } else {
                    redirect();
                }
            });
            timers.cancelDialog.ref = setTimeout(redirect, timers.cancelDialog.t);
        }, timers.stopTicker.t);
    
        timers.ticker.ref = setInterval(displayCountdown.bind(null, Date.now() + timers.stopTicker.t), timers.ticker.t);
    };
    

    Note that redirect() will be called in two possible ways :

    1. in response to the dialog being cancelled.
    2. in response to expiry of the 2 minutes.

    The callback passed to showDialog() isn't strictly necessary but allows the dialog to be completely agnostic of the consequences of its outcome. All that logic is in the caller, timer().


    Another, arguably cleaner, approach is to promisify the dialog.

    Here, everything remains the same as above except showDialog() and timer().

    // set up a promisified dialog
    function showDialog() {
        return $.Deferred(function(dfrd) {
            $("#alert div").dialog('destroy').dialog({
                'title': $("#alert span").length ? $("#alert span").text() : '---',
                'modal': true,
                'buttons': {
                    'Ok': dfrd.resolve, // yay!
                    'Cancel': dfrd.reject // yay!
                }
            }).dialog('open');
        }).always(function() {
            $("#alert div").dialog('close');
        });
    }
    
    function timer() {
        clearTimeout(timers.ticker.ref);
        clearTimeout(timers.stopTicker.ref);
        clearTimeout(timers.cancelDialog.ref);
        timers.stopTicker.ref = setTimeout(function() {
            clearInterval(timers.ticker.ref);
            $.Deferred(function(dfrd) { // "race" pattern
                showDialog().then(dfrd.resolve, dfrd.reject);
                timers.cancelDialog.ref = setTimeout(dfrd.reject, timers.cancelDialog.t);
            }).then(timer, redirect); // yay-yay!
        }, timers.stopTicker.t);
    
        timers.ticker.ref = setInterval(displayCountdown.bind(null, Date.now() + timers.stopTicker.t), timers.ticker.t);
    };