Inroduction

This article explains how to implement a dynamic countdown message in the portal. This can be used in various scenarios where a countdown or other variable, dynamic information is updated in the message, like session timeouts, limited-time offers, etc.

For example, it can be a message like “You have MM:SS remaining to complete this action”.

Approach

The idea is to get the base message with gs.getMessage() in the server script and replace the dynamically the timer using a client script.

Why not simply use a static Angular message

Angular offers a way to bind dynamic data to the HTML template, but it has limitations when the message itself is partially static with only one part that change dynamically. To my knowledge there isn’t a way to easily replace a placeholder (like “MM:SS”) within the message itself every second.

Implementation

Server Script

In the Server Script, we basically retrieve the messages and the different options for the time options.

// get the widget options
data.timer = options.timeout; // initialize the timer, in milliseconds
data.timeout = options.timeout; // keep the timeout value, used when resetting the time

// get the count down message
data.countDownMessage = gs.getMessage(options.countdownmessage);

Client script

In the Client Script, we first need a function to format time in the “MM:SS” format and set the timer value used in the message:

// Set the initial value of the time to display to the user
c.data.timeMMSS = formatMillisecondsToMMSS(c.data.timer);

// Function to convert the millisecond in minutes:seconds (MM:SS) format
function formatMillisecondsToMMSS(milliseconds){ 
	var seconds = parseInt(milliseconds / 1000, 10); 
	var minutes = parseInt(seconds / 60, 10);   
	seconds = seconds % 60;
	minutes = minutes % 60;   
	var formattedTime = minutes.toString().padStart(2, '0') + ":" + seconds.toString().padStart(2, '0');   
	return formattedTime;
}

Then we need to set the timer for the countdown, that will update the time displayed to the user:

// Start updating the timer every second
$timeout(updateTime, 1000);
function updateTime(){
	c.data.timer -= 1000; // Decrease the timer by 1 second

	// check if the time is over
	if (c.data.timer <= 0) {
		// Set the displayed time to zero (so we never show negative time)
		c.data.timeMMSS = formatMillisecondsToMMSS("0");
		// perform the required action when time is over, for example redirect somewhere else
		window.location.href = "/somewhere.do";
	} else /*update counter*/ {
		c.data.timeMMSS = formatMillisecondsToMMSS(c.data.timer);
		$timeout(updateTime, 1000); // Update again after 1 second
	}
}

In some cases, we want to reset the countdown, for example when the user is interacting with the page:

// Reset the timer when navigating in the page 
$rootScope.$on('$locationChangeStart', function(event, newUrl, oldUrl) {
	resetTimer();
});  

// Reset the timer on click and keydown events
document.addEventListener('click', resetTimer); 
document.addEventListener('keydown', resetTimer);   

// Function to reset the timer 
function resetTimer() { 
	// reset the timer 
	c.data.timer = c.data.timeout; 
	c.data.timeMMSS = formatMillisecondsToMMSS(c.data.timer);
}

HTML template

And the last, but not the least, here is the HTML template. To display the dynamic message, it call the function formatCountDownMessage() with the message and the time in the MM:SS format.

<span class="countdownmessage">{{c.formatCountDownMessage(data.countDownMessage, data.timeMMSS)}}</span>

and the corresponding function (in the client script!):

// Add the formatCountDownMessage
c.formatCountDownMessage = function(message, timeMMSS) { 
    if (!message || !timeMMSS) return message; 
    return message.replace('MM:SS', timeMMSS);
};

Putting all together in the Client Script

Here is the complete Client Script with all the part detailled above. You can also find the complete widget in the References section below.

api.controller = function($timeout, $rootScope) {
    /* widget controller */
    var c = this;

    // Set the initial value of the time to display to the user
    c.data.timeMMSS = formatMillisecondsToMMSS(c.data.timer);

    // Start updating the timer every second
    $timeout(updateTime, 1000);

    // Reset the timer when navigating in the page 
    $rootScope.$on('$locationChangeStart', function(event, newUrl, oldUrl) {
        resetTimer();
    });

    // Reset the timer on click and keydown events
    document.addEventListener('click', resetTimer);
    document.addEventListener('keydown', resetTimer);

    // Function to update the timer 
    function updateTime() {
        c.data.timer -= 1000 // Decrease the timer by 1 second
			
        // check if the time is over
        if (c.data.timer <= 0) {
            // Set the displayed time to zero (so we never show negative time)
            c.data.timeMMSS = formatMillisecondsToMMSS("0");
            // perform the required action when time is over, for example redirect somewhere else
            window.location.href = "/somewhere.do";
        } else /*update counter*/ {
            c.data.timeMMSS = formatMillisecondsToMMSS(c.data.timer);
            $timeout(updateTime, 1000);//  Update again after 1 second
        }
    }
    // Function to reset the timer 
    function resetTimer() {
        // reset the timer 
        c.data.timer = c.data.timeout;
        c.data.timeMMSS = formatMillisecondsToMMSS(c.data.timer);
    }

    // Function to convert the millisecond in minutes:seconds (MM:SS) format
    function formatMillisecondsToMMSS(milliseconds) {
        var seconds = parseInt(milliseconds / 1000, 10);
        var minutes = parseInt(seconds / 60, 10);
        seconds = seconds % 60;
        minutes = minutes % 60;
        var formattedTime = minutes.toString().padStart(2, '0') + ":" + seconds.toString().padStart(2, '0');
        return formattedTime;
    }

    // Add the formatCountDownMessage
    c.formatCountDownMessage = function(message, timeMMSS) {
			if (!message || !timeMMSS) return message;
			
        return message.replace('MM:SS', timeMMSS);
    };

};

References

1.A complete example widget is availlable in github