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.