79b4825afe5c22c7d6226037ac81ee69

Hello Everyone.
I'm trying to create a countdown timer in javascript but it's a bit cluttered. Ideally I would like it to be unobtrusive in Prototype.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<div id="countdown"></div>
<div id="notifier"></div>

<script type="text/javascript">

  var start = 20;
  Number.prototype.toMinutesAndSeconds = function() {
	var nbr = Math.floor(this / 60);
	return (nbr+":")+(((nbr=(this-(nbr*60)))<10)?"0"+nbr:nbr);
}

function display(seconds, output) {
	output.innerHTML = (--seconds).toMinutesAndSeconds();
	if(seconds > 0) {
		window.setTimeout(function(){display(seconds, output)}, 1000);
	}
	
	if(seconds < 11) {
		document.getElementById("notifier").innerHTML = "Just 10 seconds to go";
	}
	
	if (seconds == 0) {
		document.getElementById("notifier").innerHTML = "Time is up baby";
	}
}
display(start, document.getElementById("countdown"));

</script>

Refactorings

No refactoring yet !

9d8de42cebeeaf6d223d86bb9130832b

Glen Solsberry

June 27, 2008, June 27, 2008 21:06, permalink

No rating. Login to rate!

What about the following?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<div id="countdown"></div>
<div id="notifier"></div>

<script type="text/javascript">

  var start = 20;
  Number.prototype.toMinutesAndSeconds = function() {
	var m = Math.floor(this / 60);
	var s = this % m;
	//return (nbr+":")+(((nbr=(this-(nbr*60)))<10)?"0"+nbr:nbr);
	return (nbr+":"+(s<10?"0":"")+s);
}

function display(seconds, output) {
	output.innerHTML = (--seconds).toMinutesAndSeconds();
	if(seconds > 0) {
		window.setTimeout(function(){display(seconds, output)}, 1000);
	}
	
	var notifier = document.getElementById("notifier");
	if(seconds < 11) {
		notifier.innerHTML = "Just 10 seconds to go";
	}
	
	if (seconds == 0) {
		notifier.innerHTML = "Time is up baby";
	}
}
display(start, document.getElementById("countdown"));

</script>
Ee6004d792f0e95649571bfe483daf65

Ryan Cooke

June 29, 2008, June 29, 2008 21:32, permalink

1 rating. Login to rate!

Here's an OO implementation. Theres a contextual assignment in _TDisplay.prototype.tick that might seem backwards to you if you aren't familiar with the prototype model.

Above refactoring had a couple of NaN bugs in Number.prototype.toMinutesAndSeconds that I also fixed.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<div id="countdown"></div>
<div id="notifier"></div>

<script type="text/javascript">

Number.prototype.toMinutesAndSeconds = function() {
	var m = Math.floor(this / 60);
	var s = this % 60;
	return (m+":"+(s<10?"0":"")+s);
}

function _TDisplay() {}
_TDisplay.prototype.init = function(seconds, output, notify) {
	this.tot = seconds;
	this.out = output;
	this.not = notify;
	this.ii = setInterval(this.tick, 1000);
};
_TDisplay.prototype._tick = function() {
    this.out.innerHTML = (--this.tot).toMinutesAndSeconds();
    if(!this.tot) {
    	this.not.innerHTML = 'Time is up baby';
    	clearInterval(this.ii);
    }
    
    if(this.tot == 10) {
    	this.not.innerHTML = 'Just 10 seconds to go';
    }
}
TDisplay = new _TDisplay();
_TDisplay.prototype.tick = function() {
	TDisplay._tick.call(TDisplay);
}



/*using Prototype framework?
Event.observe(window, 'onload', function() {
	TDisplay.init(20, document.getElementById('countdown'), document.getElementById('notifier'));
});
*/

/*using mootools framework?
window.addEvent('domready', function() {
	TDisplay.init(20, document.getElementById('countdown'), document.getElementById('notifier'));
});
*/
	
//otherwise
pre = (window.onload) ? window.onload : function () {};
window.onload = function () {
	pre();
	TDisplay.init(20, document.getElementById('countdown'), document.getElementById('notifier'));
}

</script>
729442eea8d8548842a6e0947e333c7b

Chris Jester-Young

June 29, 2008, June 29, 2008 22:23, permalink

No rating. Login to rate!

@Ryan: Yes, it seems backwards to me---why would a "prototype" function refer to a specific instance of _TDisplay? Is it meant to model the singleton pattern? A not-so-singleton approach would be to assign the closure to "TDisplay.tick", not "_TDisplay.prototype.tick". (I can understand the use of "TDisplay._tick.call(TDisplay)", because the callback from "setInterval" loses the "this" reference.)

Of course, a not-so-singleton approach would also assign the "tick" property in the constructor, rather than assigning the "tick" property in "global" code. :-) Something like the code box below.

(ETA: My original version didn't take into account that the "this" reference might not be saved as part of the closure's environment. This will sort that out. Also, calling self._tick() will automatically set the "this" reference correctly, no need to use call().)

1
2
3
4
5
6
function _TDisplay() {
    var self = this;
    this.tick = function () {
        self._tick();
    };
}
5997d626098b88721c9393c68f633fc1

Ryan Cooke

July 3, 2008, July 03, 2008 04:10, permalink

No rating. Login to rate!

@Chris: I used my prototyping method mostly because closures leak memory in ie. Depending on the usage, they can be debilitating in many seemingly-benign code blocks, so I try to avoid them at all costs. Its also borrowing from the idea of programming to an interface, but you are correct to point out the singleton constraint of my "_TDisplay.prototype.tick" assignment. I've corrected that below. This will let you create as many closure-free timers as you want, and you can think of it as implementing the _TDisplay "interface" and utilizing javascript's wonderful ability of execution in arbitrary contexts.

1
2
3
4
TDisplay = new _TDisplay();
TDisplay.tick = function() {
	TDisplay._tick.call(TDisplay);
}
5071c5b861341c0dcfcf6ac86327701f

Tien Dung

July 24, 2008, July 24, 2008 01:03, permalink

No rating. Login to rate!

Here is my refactor code. Make use of JavaScript idioms and functional style. Also use module pattern (function (){})(); to reduce conflict.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<html>
<body>
<div id="countdown"></div>
<div id="notifier"></div>

<script type="text/javascript">

(function () {
  function display( notifier, str ) {
    document.getElementById(notifier).innerHTML = str;
  }
	
  function toMinuteAndSecond( x ) {
    return Math.floor(x/60) + ":" + (x=x%60 < 10 ? 0 : x);
  }
	
  function setTimer( remain, actions ) {
    var action;
    (function countdown() {
       display("countdown", toMinuteAndSecond(remain));		
       if (action = actions[remain]) {
         action();
       }
       if (remain > 0) {
         remain -= 1;
         setTimeout(arguments.callee, 1000);
       }
    })(); // End countdown
  }

  setTimer(20, {
    10: function () { display("notifier", "Just 10 seconds to go"); },
     5: function () { display("notifier", "5 seconds left");        },
     0: function () { display("notifier", "Time is up baby");       }
  });	
})();

</script>
</body>
</html>
5071c5b861341c0dcfcf6ac86327701f

Tien Dung

July 24, 2008, July 24, 2008 01:12, permalink

2 ratings. Login to rate!

Compact version

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<html>
<body>
<div id="countdown"></div>
<div id="notifier"></div>
<script type="text/javascript">

  function display( notifier, str ) {
    document.getElementById(notifier).innerHTML = str;
  }
	
  function toMinuteAndSecond( x ) {
    return Math.floor(x/60) + ":" + x%60;
  }
	
  function setTimer( remain, actions ) {
    (function countdown() {
       display("countdown", toMinuteAndSecond(remain));		
       actions[remain] && actions[remain]();
       (remain -= 1) >= 0 && setTimeout(arguments.callee, 1000);
    })();
  }

  setTimer(20, {
    10: function () { display("notifier", "Just 10 seconds to go"); },
     5: function () { display("notifier", "5 seconds left");        },
     0: function () { display("notifier", "Time is up baby");       }
  });	

</script>
</body>
</html>
79b4825afe5c22c7d6226037ac81ee69

Jermaine

July 24, 2008, July 24, 2008 06:50, permalink

No rating. Login to rate!

Guys, this helped me alot. Thanks so much!

4592f7edcb3d4acb437774c4e2d784c1

steve

July 28, 2008, July 28, 2008 12:39, permalink

No rating. Login to rate!

guys - awesome script. great resource for code idiots like myself. one question...on this last compact version Tien - when the timer gets to the last 10 seconds of any minute (setting for multiple minutes) it only displays the "0" and not the other number after. i.e. the last ten seconds of the 42nd minute show "42:0", until it hits "39:59" and continues. Any idea how to fix that? Thanks!

5071c5b861341c0dcfcf6ac86327701f

Tien Dung

July 28, 2008, July 28, 2008 23:43, permalink

No rating. Login to rate!

Hi Steve, just change to function display to Math.floor(x/60) + ":" + x%60;

I changed the compact version in case you need full example.

C510febb9bed68b5cc4a09f076701e0f

AnonymousBot

August 1, 2008, August 01, 2008 20:22, permalink

No rating. Login to rate!

Change line 12 to read:

1
return Math.floor(x/60) + ":" + (x<10?"0":"")+x%60;
C510febb9bed68b5cc4a09f076701e0f

AnonymousBot

August 1, 2008, August 01, 2008 20:45, permalink

No rating. Login to rate!

Sorry, one slight correction. The above only worked for seconds under 10 (meaning 0:10 and below). This should work for all seconds under 10, regardless of the minute. Make the change below to line 12 of Tien Dungs compact version code.

1
return Math.floor(x/60) + ":" + (x%60<10?"0":"")+x%60;

Your refactoring





Format Copy from initial code

or Cancel