PageSourceSearch

https://www.froster.org/forum/jscripts/jquery/countdown/jquery.countdown.js

js froster.org collected 2026-09-24 22:49:16 UTC 19,177 bytes, 508 lines download raw bytes

1/* http://keith-wood.name/countdown.html
2   Countdown for jQuery v1.4.2.
3   Written by Keith Wood ([email protected]) January 2008.
4   Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and 
5   MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses. 
6   Please attribute the author if you use it. */
7
8/* Display a countdown timer.
9   Attach it with options like:
10   $('div selector').countdown(
11       {until: new Date(2009, 1 - 1, 1, 0, 0, 0), onExpiry: happyNewYear}); */
12
13(function($) { // Hide scope, no $ conflict
14
15/* Countdown manager. */
16function Countdown() {
17	this.regional = []; // Available regional settings, indexed by language code
18	this.regional[''] = { // Default regional settings
19		// The display texts for the counters
20		labels: ['Years', 'Months', 'Weeks', 'Days', 'Hours', 'Minutes', 'Seconds'],
21		// The display texts for the counters if only one
22		labels1: ['Year', 'Month', 'Week', 'Day', 'Hour', 'Minute', 'Second'],
23		compactLabels: ['y', 'm', 'w', 'd'], // The compact texts for the counters
24		timeSeparator: ':', // Separator for time periods
25		isRTL: false // True for right-to-left languages, false for left-to-right
26	};
27	this._defaults = {
28		format: 'dHMS', // Format for display - upper case for always, lower case only if non-zero,
29			// 'Y' years, 'O' months, 'W' weeks, 'D' days, 'H' hours, 'M' minutes, 'S' seconds
30		layout: '', // Build your own layout for the countdown
31		compact: false, // True to display in a compact format, false for an expanded one
32		description: '', // The description displayed for the countdown
33		expiryUrl: null, // A URL to load upon expiry, replacing the current page
34		alwaysExpire: false, // True to trigger onExpiry even if never counted down
35		onExpiry: null, // Callback when the countdown expires -
36			// receives no parameters and 'this' is the containing division
37		onTick: null, // Callback when the countdown is updated -
38			// receives int[7] being the breakdown by period (based on format)
39			// and 'this' is the containing division
40		serverTime: null // The current time on the server, to calculate an offset for other time zones
41	};
42	$.extend(this._defaults, this.regional['']);
43}
44
45var PROP_NAME = 'countdown';
46
47var Y = 0; // Years
48var O = 1; // Months
49var W = 2; // Weeks
50var D = 3; // Days
51var H = 4; // Hours
52var M = 5; // Minutes
53var S = 6; // Seconds
54
55$.extend(Countdown.prototype, {
56	/* Class name added to elements to indicate already configured with countdown. */
57	markerClassName: 'hasCountdown',
58	
59	/* Override the default settings for all instances of the countdown widget.
60	   @param  options  object - the new settings to use as defaults */
61	setDefaults: function(options) {
62		this._resetExtraLabels(this._defaults, options);
63		extendRemove(this._defaults, options || {});
64	},
65
66	/* Attach the countdown widget to a div.
67	   @param  target   element - the containing division
68	   @param  options  object - the initial settings for the countdown */
69	_attachCountdown: function(target, options) {
70		target = $(target);
71		if (target.is('.' + this.markerClassName)) {
72			return;
73		}
74		target.addClass(this.markerClassName);
75		if (!target[0].id) {
76			target[0].id = 'cdn' + new Date().getTime();
77		}
78		var inst = {};
79		inst.options = $.extend({}, options);
80		inst._periods = [0, 0, 0, 0, 0, 0, 0];
81		this._adjustSettings(inst);
82		$.data(target[0], PROP_NAME, inst);
83		this._updateCountdown(target, inst);
84	},
85
86	/* Redisplay the countdown with an updated display.
87	   @param  id    element or string - the containing division or its ID
88	   @param  inst  object - the current settings for this instance */
89	_updateCountdown: function(id, inst) {
90		var target = $(id);
91		inst = inst || $.data(target[0], PROP_NAME);
92		if (!inst) {
93			return;
94		}
95		target.html(this._generateHTML(inst));
96		target[(this._get(inst, 'isRTL') ? 'add' : 'remove') + 'Class']('countdown_rtl');
97		var onTick = this._get(inst, 'onTick');
98		if (onTick) {
99			onTick.apply(target[0], [inst._hold != 'lap' ? inst._periods :
100				this._calculatePeriods(inst, inst._show, new Date())]);
101		}
102		var expired = inst._hold != 'pause' &&
103			(inst._since ? inst._now.getTime() <= inst._since.getTime() :
104			inst._now.getTime() >= inst._until.getTime());
105		if (expired) {
106			if (inst._timer || this._get(inst, 'alwaysExpire')) {
107				var onExpiry = this._get(inst, 'onExpiry');
108				if (onExpiry) {
109					onExpiry.apply(target[0], []);
110				}
111				var expiryUrl = this._get(inst, 'expiryUrl');
112				if (expiryUrl) {
113					window.location = expiryUrl;
114				}
115			}
116			inst._timer = null;
117		}
118		else if (inst._hold == 'pause') {
119			inst._time = null;
120		}
121		else {
122			var format = this._get(inst, 'format');
123			inst._timer = setTimeout('$.countdown._updateCountdown("#' + target[0].id + '")',
124				(format.match('s|S') ? 1 : (format.match('m|M') ? 30 : 600)) * 980);  // just under the full time
125		}
126		$.data(target[0], PROP_NAME, inst);
127	},
128
129	/* Reconfigure the settings for a countdown div.
130	   @param  target   element - the containing division
131	   @param  options  object - the new settings for the countdown */
132	_changeCountdown: function(target, options) {
133		var inst = $.data(target, PROP_NAME);
134		if (inst) {
135			this._resetExtraLabels(inst.options, options);
136			extendRemove(inst.options, options || {});
137			this._adjustSettings(inst);
138			$.data(target, PROP_NAME, inst);
139			this._updateCountdown(target, inst);
140		}
141	},
142
143	/* Reset any extra labelsn and compactLabelsn entries if changing labels.
144	   @param  base     (object) the options to be updated
145	   @param  options  (object) the new option values */
146	_resetExtraLabels: function(base, options) {
147		var changingLabels = false;
148		for (var n in options) {
149			if (n.match(/[Ll]abels/)) {
150				changingLabels = true;
151				break;
152			}
153		}
154		if (changingLabels) {
155			for (var n in base) { // Remove custom numbered labels
156				if (n.match(/[Ll]abels[0-9]/)) {
157					base[n] = null;
158				}
159			}
160		}
161	},
162
163	/* Remove the countdown widget from a div.
164	   @param  target  element - the containing division */
165	_destroyCountdown: function(target) {
166		target = $(target);
167		if (!target.is('.' + this.markerClassName)) {
168			return;
169		}
170		target.removeClass(this.markerClassName).empty();
171		var inst = $.data(target[0], PROP_NAME);
172		if (inst._timer) {
173			clearTimeout(inst._timer);
174		}
175		$.removeData(target[0], PROP_NAME);
176	},
177
178	/* Pause a countdown widget at the current time.
179	   Stop it running but remember and display the current time.
180	   @param  target  element - the containing division */
181	_pauseCountdown: function(target) {
182		this._hold(target, 'pause');
183	},
184
185	/* Pause a countdown widget at the current time.
186	   Stop the display but keep the countdown running.
187	   @param  target  element - the containing division */
188	_lapCountdown: function(target) {
189		this._hold(target, 'lap');
190	},
191
192	/* Resume a paused countdown widget.
193	   @param  target  element - the containing division */
194	_resumeCountdown: function(target) {
195		this._hold(target, null);
196	},
197
198	/* Pause or resume a countdown widget.
199	   @param  target  element - the containing division
200	   @param  hold    string - the new hold setting */
201	_hold: function(target, hold) {
202		var inst = $.data(target, PROP_NAME);
203		if (inst) {
204			if (inst._hold == 'pause' && !hold) {
205				inst._periods = inst._savePeriods;
206				var sign = (inst._since ? '-' : '+');
207				inst[inst._since ? '_since' : '_until'] =
208					this._determineTime(sign + inst._periods[0] + 'Y' +
209						sign + inst._periods[1] + 'O' + sign + inst._periods[2] + 'W' +
210						sign + inst._periods[3] + 'D' + sign + inst._periods[4] + 'H' + 
211						sign + inst._periods[5] + 'M' + sign + inst._periods[6] + 'S');
212			}
213			inst._hold = hold;
214			inst._savePeriods = (hold == 'pause' ? inst._periods : null);
215			$.data(target, PROP_NAME, inst);
216			this._updateCountdown(target, inst);
217		}
218	},
219
220	/* Return the current time periods.
221	   @param  target  element - the containing division
222	   @return  number[7] - the current periods for the countdown */
223	_getTimesCountdown: function(target) {
224		var inst = $.data(target, PROP_NAME);
225		return (!inst ? null : (!inst._hold ? inst._periods :
226			this._calculatePeriods(inst, inst._show, new Date())));
227	},
228
229	/* Get a setting value, defaulting if necessary.
230	   @param  inst  object - the current settings for this instance
231	   @param  name  string - the name of the required setting
232	   @return  any - the setting's value or a default if not overridden */
233	_get: function(inst, name) {
234		return (inst.options[name] != null ?
235			inst.options[name] : $.countdown._defaults[name]);
236	},
237	
238	/* Calculate interal settings for an instance.
239	   @param  inst  object - the current settings for this instance */
240	_adjustSettings: function(inst) {
241		var now = new Date();
242		var serverTime = this._get(inst, 'serverTime');
243		inst._offset = (serverTime ? serverTime.getTime() - now.getTime() : 0);
244		inst._since = this._get(inst, 'since');
245		if (inst._since) {
246			inst._since = this._determineTime(inst._since, null);
247		}
248		inst._until = this._determineTime(this._get(inst, 'until'), now);
249		inst._show = this._determineShow(inst);
250	},
251
252	/* A time may be specified as an exact value or a relative one.
253	   @param  setting      string or number or Date - the date/time value
254	           as a relative or absolute value
255	   @param  defaultTime  Date - the date/time to use if no other is supplied
256	   @return  Date - the corresponding date/time */
257	_determineTime: function(setting, defaultTime) {
258		var offsetNumeric = function(offset) { // e.g. +300, -2
259			var time = new Date();
260			time.setTime(time.getTime() + offset * 1000);
261			return time;
262		};
263		var getDaysInMonth = function(year, month) {
264			return 32 - new Date(year, month, 32).getDate();
265		};
266		var offsetString = function(offset) { // e.g. '+2d', '-4w', '+3h +30m'
267			var time = new Date();
268			var year = time.getFullYear();
269			var month = time.getMonth();
270			var day = time.getDate();
271			var hour = time.getHours();
272			var minute = time.getMinutes();
273			var second = time.getSeconds();
274			var pattern = /([+-]?[0-9]+)\s*(s|S|m|M|h|H|d|D|w|W|o|O|y|Y)?/g;
275			var matches = pattern.exec(offset);
276			while (matches) {
277				switch (matches[2] || 's') {
278					case 's' : case 'S' :
279						second += parseInt(matches[1]); break;
280					case 'm' : case 'M' :
281						minute += parseInt(matches[1]); break;
282					case 'h' : case 'H' :
283						hour += parseInt(matches[1]); break;
284					case 'd' : case 'D' :
285						day += parseInt(matches[1]); break;
286					case 'w' : case 'W' :
287						day += parseInt(matches[1]) * 7; break;
288					case 'o' : case 'O' :
289						month += parseInt(matches[1]); 
290						day = Math.min(day, getDaysInMonth(year, month));
291						break;
292					case 'y': case 'Y' :
293						year += parseInt(matches[1]);
294						day = Math.min(day, getDaysInMonth(year, month));
295						break;
296				}
297				matches = pattern.exec(offset);
298			}
299			time = new Date(year, month, day, hour, minute, second, 0);
300			return time;
301		};
302		var time = (setting == null ? defaultTime :
303			(typeof setting == 'string' ? offsetString(setting) :
304			(typeof setting == 'number' ? offsetNumeric(setting) : setting)));
305		if (time) time.setMilliseconds(0);
306		return time;
307	},
308
309	/* Generate the HTML to display the countdown widget.
310	   @param  inst  object - the current settings for this instance
311	   @return  string - the new HTML for the countdown display */
312	_generateHTML: function(inst) {
313		// Determine what to show
314		inst._periods = periods = (inst._hold ? inst._periods :
315			this._calculatePeriods(inst, inst._show, new Date()));
316		// Show all 'asNeeded' after first non-zero value
317		var shownNonZero = false;
318		var showCount = 0;
319		for (var period = 0; period < inst._show.length; period++) {
320			shownNonZero |= (inst._show[period] == '?' && periods[period] > 0);
321			inst._show[period] = (inst._show[period] == '?' && !shownNonZero ? null : inst._show[period]);
322			showCount += (inst._show[period] ? 1 : 0);
323		}
324		var compact = this._get(inst, 'compact');
325		var layout = this._get(inst, 'layout');
326		var labels = (compact ? this._get(inst, 'compactLabels') : this._get(inst, 'labels'));
327		var timeSeparator = this._get(inst, 'timeSeparator');
328		var description = this._get(inst, 'description') || '';
329		var twoDigits = function(value) {
330			return (value < 10 ? '0' : '') + value;
331		};
332		var showCompact = function(period) {
333			var labelsNum = $.countdown._get(inst, 'compactLabels' + periods[period]);
334			return (inst._show[period] ? periods[period] +
335				(labelsNum ? labelsNum[period] : labels[period]) + ' ' : '');
336		};
337		var showFull = function(period) {
338			var labelsNum = $.countdown._get(inst, 'labels' + periods[period]);
339			return (inst._show[period] ?
340				'<div class="countdown_section"><span class="countdown_amount">' +
341				periods[period] + '</span><br/>' +
342				(labelsNum ? labelsNum[period] : labels[period]) + '</div>' : '');
343		};
344		return (layout ? this._buildLayout(inst, layout, compact) :
345			((compact ? // Compact version
346			'<div class="countdown_row countdown_amount' +
347			(inst._hold ? ' countdown_holding' : '') + '">' + 
348			showCompact(Y) + showCompact(O) + showCompact(W) + showCompact(D) + 
349			(inst._show[H] ? twoDigits(periods[H]) : '') +
350			(inst._show[M] ? (inst._show[H] ? timeSeparator : '') + twoDigits(periods[M]) : '') +
351			(inst._show[S] ? (inst._show[H] || inst._show[M] ? timeSeparator : '') +
352			twoDigits(periods[S]) : '') :
353			// Full version
354			'<div class="countdown_row countdown_show' + showCount +
355			(inst._hold ? ' countdown_holding' : '') + '">' +
356			showFull(Y) + showFull(O) + showFull(W) + showFull(D) +
357			showFull(H) + showFull(M) + showFull(S)) + '</div>' +
358			(description ? '<div class="countdown_row countdown_descr">' + description + '</div>' : '')));
359	},
360
361	/* Construct a custom layout.
362	   @param  inst     (object) the current settings for this instance
363	   @param  layout   (string) the customised layout
364	   @param  compact  (boolean) true if using compact labels
365	   @return  string - the custom HTML */
366	_buildLayout: function(inst, layout, compact) {
367		var labels = (compact ? this._get(inst, 'compactLabels') : this._get(inst, 'labels'));
368		var html = layout;
369		var processPeriod = function(period, index) {
370			var pattern1 = new RegExp('%' + period + '.*%' + period);
371			var pattern2 = new RegExp('%' + period + '.*');
372			while (true) {
373				var matches = pattern1.exec(html);
374				if (!matches) {
375					break;
376				}
377				matches[0] = matches[0].substr(0, 2) +
378					matches[0].substr(2).replace(pattern2, '%' + period);
379				html = html.replace(matches[0], inst._show[index] ?
380					customisePeriod(matches[0], period, index) : '');
381			}
382		};
383		var customisePeriod = function(text, period, index) {
384			var labelsNum = $.countdown._get(inst,
385				(compact ? 'compactLabels' : 'labels') + inst._periods[index]);
386			return text.substr(2, text.length - 4).
387				replace(/%nn/g, (inst._periods[index] < 10 ? '0' : '') +
388				inst._periods[index]).
389				replace(/%n/g, inst._periods[index]).
390				replace(/%l/g, (labelsNum ? labelsNum[index] : labels[index]));
391		};
392		processPeriod('Y', Y);
393		processPeriod('O', O);
394		processPeriod('W', W);
395		processPeriod('D', D);
396		processPeriod('H', H);
397		processPeriod('M', M);
398		processPeriod('S', S);
399		return html;
400	},
401
402	/* Translate the format into flags for each period.
403	   @param  inst  object - the current settings for this instance
404	   @return  string[7] - flags indicating which periods are requested (?) or
405	            required (!) by year, month, week, day, hour, minute, second */
406	_determineShow: function(inst) {
407		var format = this._get(inst, 'format');
408		var show = [];
409		show[Y] = (format.match('y') ? '?' : (format.match('Y') ? '!' : null));
410		show[O] = (format.match('o') ? '?' : (format.match('O') ? '!' : null));
411		show[W] = (format.match('w') ? '?' : (format.match('W') ? '!' : null));
412		show[D] = (format.match('d') ? '?' : (format.match('D') ? '!' : null));
413		show[H] = (format.match('h') ? '?' : (format.match('H') ? '!' : null));
414		show[M] = (format.match('m') ? '?' : (format.match('M') ? '!' : null));
415		show[S] = (format.match('s') ? '?' : (format.match('S') ? '!' : null));
416		return show;
417	},
418	
419	/* Calculate the requested periods between now and the target time.
420	   @param  inst  object - the current settings for this instance
421	   @param  show  string[7] - flags indicating which periods are requested/required
422	   @param  now   Date - the current date and time
423	   @return  number[7] - the current time periods (always positive)
424	            by year, month, week, day, hour, minute, second */
425	_calculatePeriods: function(inst, show, now) {
426		// Find endpoints
427		inst._now = now;
428		inst._now.setMilliseconds(0);
429		var until = new Date(inst._now.getTime());
430		if (inst._since && now.getTime() < inst._since.getTime()) {
431			inst._now = now = until;
432		}
433		else if (inst._since) {
434			now = inst._since;
435		}
436		else {
437			until.setTime(inst._until.getTime());
438			if (now.getTime() > inst._until.getTime()) {
439				inst._now = now = until;
440		}
441		}
442		until.setTime(until.getTime() - inst._offset); // Adjust for time zone
443		// Calculate differences by period
444		var periods = [0, 0, 0, 0, 0, 0, 0];
445		if (show[Y] || show[O]) {
446			var months = Math.max(0, (until.getFullYear() - now.getFullYear()) * 12 +
447				until.getMonth() - now.getMonth() + (until.getDate() < now.getDate() ? -1 : 0));
448			periods[Y] = (show[Y] ? Math.floor(months / 12) : 0);
449			periods[O] = (show[O] ? months - periods[Y] * 12 : 0);
450			if (inst._since) {
451				until.setFullYear(until.getFullYear() - periods[Y]);
452				until.setMonth(until.getMonth() - periods[O]);
453			}
454			else {
455			now = new Date(now.getTime());
456			now.setFullYear(now.getFullYear() + periods[Y]);
457			now.setMonth(now.getMonth() + periods[O]);
458		}
459		}
460		var diff = Math.floor((until.getTime() - now.getTime()) / 1000);
461		var extractPeriod = function(period, numSecs) {
462			periods[period] = (show[period] ? Math.floor(diff / numSecs) : 0);
463			diff -= periods[period] * numSecs;
464		};
465		extractPeriod(W, 604800);
466		extractPeriod(D, 86400);
467		extractPeriod(H, 3600);
468		extractPeriod(M, 60);
469		extractPeriod(S, 1);
470		return periods;
471	}
472});
473
474/* jQuery extend now ignores nulls! */
475function extendRemove(target, props) {
476	$.extend(target, props);
477	for (var name in props) {
478		if (props[name] == null) {
479			target[name] = null;
480		}
481	}
482	return target;
483}
484
485/* Attach the countdown functionality to a jQuery selection.
486   @param  command  string - the command to run (optional, default 'attach')
487   @param  options  object - the new settings to use for these countdown instances
488   @return  jQuery object - for chaining further calls */
489$.fn.countdown = function(options) {
490	var otherArgs = Array.prototype.slice.call(arguments, 1);
491	if (options == 'getTimes') {
492		return $.countdown['_' + options + 'Countdown'].
493			apply($.countdown, [this[0]].concat(otherArgs));
494	}
495	return this.each(function() {
496		if (typeof options == 'string') {
497			$.countdown['_' + options + 'Countdown'].apply($.countdown, [this].concat(otherArgs));
498		}
499		else {
500			$.countdown._attachCountdown(this, options);
501		}
502	});
503};
504
505/* Initialise the countdown functionality. */
506$.countdown = new Countdown(); // singleton instance
507
508})(jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.