--- loncom/html/adm/countdown/jquery.countdown.js 2012/03/18 13:51:11 1.1 +++ loncom/html/adm/countdown/jquery.countdown.js 2014/01/15 14:45:11 1.2 @@ -1,8 +1,7 @@ /* http://keith-wood.name/countdown.html - Countdown for jQuery v1.5.11. + Countdown for jQuery v1.6.3. Written by Keith Wood (kbwood{at}iinet.com.au) January 2008. - Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and - MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses. + Available under the MIT (https://github.com/jquery/jquery/blob/master/MIT-LICENSE.txt) license. Please attribute the author if you use it. */ /* Display a countdown timer. @@ -22,6 +21,7 @@ function Countdown() { labels1: ['Year', 'Month', 'Week', 'Day', 'Hour', 'Minute', 'Second'], compactLabels: ['y', 'm', 'w', 'd'], // The compact texts for the counters whichLabels: null, // Function to determine which labels to use + digits: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], // The digits to display timeSeparator: ':', // Separator for time periods isRTL: false // True for right-to-left languages, false for left-to-right }; @@ -53,30 +53,38 @@ function Countdown() { }; $.extend(this._defaults, this.regional['']); this._serverSyncs = []; + var now = (typeof Date.now == 'function' ? Date.now : + function() { return new Date().getTime(); }); + var perfAvail = (window.performance && typeof window.performance.now == 'function'); // Shared timer for all countdowns function timerCallBack(timestamp) { - var drawStart = (timestamp || new Date().getTime()); + var drawStart = (timestamp < 1e12 ? // New HTML5 high resolution timer + (perfAvail ? (performance.now() + performance.timing.navigationStart) : now()) : + // Integer milliseconds since unix epoch + timestamp || now()); if (drawStart - animationStartTime >= 1000) { - $.countdown._updateTargets(); + plugin._updateTargets(); animationStartTime = drawStart; } requestAnimationFrame(timerCallBack); } - var requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || window.oRequestAnimationFrame || - window.msRequestAnimationFrame || null; // this is when we expect a fall-back to setInterval as it's much more fluid + var requestAnimationFrame = window.requestAnimationFrame || + window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || window.msRequestAnimationFrame || null; + // This is when we expect a fall-back to setInterval as it's much more fluid var animationStartTime = 0; - if (!requestAnimationFrame) { - setInterval(function() { $.countdown._updateTargets(); }, 980); // Fall back to good old setInterval + if (!requestAnimationFrame || $.noRequestAnimationFrame) { + $.noRequestAnimationFrame = null; + setInterval(function() { plugin._updateTargets(); }, 980); // Fall back to good old setInterval } else { - animationStartTime = window.mozAnimationStartTime || new Date().getTime(); + animationStartTime = window.animationStartTime || + window.webkitAnimationStartTime || window.mozAnimationStartTime || + window.oAnimationStartTime || window.msAnimationStartTime || now(); requestAnimationFrame(timerCallBack); } } -var PROP_NAME = 'countdown'; - var Y = 0; // Years var O = 1; // Months var W = 2; // Weeks @@ -88,6 +96,23 @@ var S = 6; // Seconds $.extend(Countdown.prototype, { /* Class name added to elements to indicate already configured with countdown. */ markerClassName: 'hasCountdown', + /* Name of the data property for instance settings. */ + propertyName: 'countdown', + + /* Class name for the right-to-left marker. */ + _rtlClass: 'countdown_rtl', + /* Class name for the countdown section marker. */ + _sectionClass: 'countdown_section', + /* Class name for the period amount marker. */ + _amountClass: 'countdown_amount', + /* Class name for the countdown row marker. */ + _rowClass: 'countdown_row', + /* Class name for the holding countdown marker. */ + _holdingClass: 'countdown_holding', + /* Class name for the showing countdown marker. */ + _showClass: 'countdown_show', + /* Class name for the description marker. */ + _descrClass: 'countdown_descr', /* List of currently active countdown targets. */ _timerTargets: [], @@ -96,7 +121,7 @@ $.extend(Countdown.prototype, { @param options (object) the new settings to use as defaults */ setDefaults: function(options) { this._resetExtraLabels(this._defaults, options); - extendRemove(this._defaults, options || {}); + $.extend(this._defaults, options || {}); }, /* Convert a date/time to UTC. @@ -141,31 +166,17 @@ $.extend(Countdown.prototype, { periods[3] * 86400 + periods[4] * 3600 + periods[5] * 60 + periods[6]; }, - /* Retrieve one or more settings values. - @param name (string, optional) the name of the setting to retrieve - or 'all' for all instance settings or omit for all default settings - @return (any) the requested setting(s) */ - _settingsCountdown: function(target, name) { - if (!name) { - return $.countdown._defaults; - } - var inst = $.data(target, PROP_NAME); - return (name == 'all' ? inst.options : inst.options[name]); - }, - /* Attach the countdown widget to a div. @param target (element) the containing division @param options (object) the initial settings for the countdown */ - _attachCountdown: function(target, options) { - var $target = $(target); - if ($target.hasClass(this.markerClassName)) { + _attachPlugin: function(target, options) { + target = $(target); + if (target.hasClass(this.markerClassName)) { return; } - $target.addClass(this.markerClassName); - var inst = {options: $.extend({}, options), - _periods: [0, 0, 0, 0, 0, 0, 0]}; - $.data(target, PROP_NAME, inst); - this._changeCountdown(target); + var inst = {options: $.extend({}, this._defaults), _periods: [0, 0, 0, 0, 0, 0, 0]}; + target.addClass(this.markerClassName).data(this.propertyName, inst); + this._optionPlugin(target, options); }, /* Add a target to the list of active ones. @@ -197,24 +208,62 @@ $.extend(Countdown.prototype, { } }, + /* Reconfigure the settings for a countdown div. + @param target (element) the control to affect + @param options (object) the new options for this instance or + (string) an individual property name + @param value (any) the individual property value (omit if options + is an object or to retrieve the value of a setting) + @return (any) if retrieving a value */ + _optionPlugin: function(target, options, value) { + target = $(target); + var inst = target.data(this.propertyName); + if (!options || (typeof options == 'string' && value == null)) { // Get option + var name = options; + options = (inst || {}).options; + return (options && name ? options[name] : options); + } + + if (!target.hasClass(this.markerClassName)) { + return; + } + options = options || {}; + if (typeof options == 'string') { + var name = options; + options = {}; + options[name] = value; + } + if (options.layout) { + options.layout = options.layout.replace(/</g, '<').replace(/>/g, '>'); + } + this._resetExtraLabels(inst.options, options); + var timezoneChanged = (inst.options.timezone != options.timezone); + $.extend(inst.options, options); + this._adjustSettings(target, inst, + options.until != null || options.since != null || timezoneChanged); + var now = new Date(); + if ((inst._since && inst._since < now) || (inst._until && inst._until > now)) { + this._addTarget(target[0]); + } + this._updateCountdown(target, inst); + }, + /* Redisplay the countdown with an updated display. @param target (jQuery) the containing division @param inst (object) the current settings for this instance */ _updateCountdown: function(target, inst) { var $target = $(target); - inst = inst || $.data(target, PROP_NAME); + inst = inst || $target.data(this.propertyName); if (!inst) { return; } - $target.html(this._generateHTML(inst)); - $target[(this._get(inst, 'isRTL') ? 'add' : 'remove') + 'Class']('countdown_rtl'); - var onTick = this._get(inst, 'onTick'); - if (onTick) { + $target.html(this._generateHTML(inst)).toggleClass(this._rtlClass, inst.options.isRTL); + if ($.isFunction(inst.options.onTick)) { var periods = inst._hold != 'lap' ? inst._periods : - this._calculatePeriods(inst, inst._show, this._get(inst, 'significant'), new Date()); - var tickInterval = this._get(inst, 'tickInterval'); - if (tickInterval == 1 || this.periodsToSeconds(periods) % tickInterval == 0) { - onTick.apply(target, [periods]); + this._calculatePeriods(inst, inst._show, inst.options.significant, new Date()); + if (inst.options.tickInterval == 1 || + this.periodsToSeconds(periods) % inst.options.tickInterval == 0) { + inst.options.onTick.apply(target, [periods]); } } var expired = inst._hold != 'pause' && @@ -222,22 +271,19 @@ $.extend(Countdown.prototype, { inst._now.getTime() >= inst._until.getTime()); if (expired && !inst._expiring) { inst._expiring = true; - if (this._hasTarget(target) || this._get(inst, 'alwaysExpire')) { + if (this._hasTarget(target) || inst.options.alwaysExpire) { this._removeTarget(target); - var onExpiry = this._get(inst, 'onExpiry'); - if (onExpiry) { - onExpiry.apply(target, []); - } - var expiryText = this._get(inst, 'expiryText'); - if (expiryText) { - var layout = this._get(inst, 'layout'); - inst.options.layout = expiryText; + if ($.isFunction(inst.options.onExpiry)) { + inst.options.onExpiry.apply(target, []); + } + if (inst.options.expiryText) { + var layout = inst.options.layout; + inst.options.layout = inst.options.expiryText; this._updateCountdown(target, inst); inst.options.layout = layout; } - var expiryUrl = this._get(inst, 'expiryUrl'); - if (expiryUrl) { - window.location = expiryUrl; + if (inst.options.expiryUrl) { + window.location = inst.options.expiryUrl; } } inst._expiring = false; @@ -245,35 +291,7 @@ $.extend(Countdown.prototype, { else if (inst._hold == 'pause') { this._removeTarget(target); } - $.data(target, PROP_NAME, inst); - }, - - /* Reconfigure the settings for a countdown div. - @param target (element) the containing division - @param options (object) the new settings for the countdown or - (string) an individual property name - @param value (any) the individual property value - (omit if options is an object) */ - _changeCountdown: function(target, options, value) { - options = options || {}; - if (typeof options == 'string') { - var name = options; - options = {}; - options[name] = value; - } - var inst = $.data(target, PROP_NAME); - if (inst) { - this._resetExtraLabels(inst.options, options); - extendRemove(inst.options, options); - this._adjustSettings(target, inst); - $.data(target, PROP_NAME, inst); - var now = new Date(); - if ((inst._since && inst._since < now) || - (inst._until && inst._until > now)) { - this._addTarget(target); - } - this._updateCountdown(target, inst); - } + $target.data(this.propertyName, inst); }, /* Reset any extra labelsn and compactLabelsn entries if changing labels. @@ -289,7 +307,7 @@ $.extend(Countdown.prototype, { } if (changingLabels) { for (var n in base) { // Remove custom numbered labels - if (n.match(/[Ll]abels[0-9]/)) { + if (n.match(/[Ll]abels[02-9]|compactLabels1/)) { base[n] = null; } } @@ -298,73 +316,75 @@ $.extend(Countdown.prototype, { /* Calculate interal settings for an instance. @param target (element) the containing division - @param inst (object) the current settings for this instance */ - _adjustSettings: function(target, inst) { + @param inst (object) the current settings for this instance + @param recalc (boolean) true if until or since are set */ + _adjustSettings: function(target, inst, recalc) { var now; - var serverSync = this._get(inst, 'serverSync'); var serverOffset = 0; var serverEntry = null; for (var i = 0; i < this._serverSyncs.length; i++) { - if (this._serverSyncs[i][0] == serverSync) { + if (this._serverSyncs[i][0] == inst.options.serverSync) { serverEntry = this._serverSyncs[i][1]; break; } } if (serverEntry != null) { - serverOffset = (serverSync ? serverEntry : 0); + serverOffset = (inst.options.serverSync ? serverEntry : 0); now = new Date(); } else { - var serverResult = (serverSync ? serverSync.apply(target, []) : null); + var serverResult = ($.isFunction(inst.options.serverSync) ? + inst.options.serverSync.apply(target, []) : null); now = new Date(); serverOffset = (serverResult ? now.getTime() - serverResult.getTime() : 0); - this._serverSyncs.push([serverSync, serverOffset]); + this._serverSyncs.push([inst.options.serverSync, serverOffset]); } - var timezone = this._get(inst, 'timezone'); + var timezone = inst.options.timezone; timezone = (timezone == null ? -now.getTimezoneOffset() : timezone); - inst._since = this._get(inst, 'since'); - if (inst._since != null) { - inst._since = this.UTCDate(timezone, this._determineTime(inst._since, null)); - if (inst._since && serverOffset) { - inst._since.setMilliseconds(inst._since.getMilliseconds() + serverOffset); + if (recalc || (!recalc && inst._until == null && inst._since == null)) { + inst._since = inst.options.since; + if (inst._since != null) { + inst._since = this.UTCDate(timezone, this._determineTime(inst._since, null)); + if (inst._since && serverOffset) { + inst._since.setMilliseconds(inst._since.getMilliseconds() + serverOffset); + } + } + inst._until = this.UTCDate(timezone, this._determineTime(inst.options.until, now)); + if (serverOffset) { + inst._until.setMilliseconds(inst._until.getMilliseconds() + serverOffset); } - } - inst._until = this.UTCDate(timezone, this._determineTime(this._get(inst, 'until'), now)); - if (serverOffset) { - inst._until.setMilliseconds(inst._until.getMilliseconds() + serverOffset); } inst._show = this._determineShow(inst); }, /* Remove the countdown widget from a div. @param target (element) the containing division */ - _destroyCountdown: function(target) { - var $target = $(target); - if (!$target.hasClass(this.markerClassName)) { + _destroyPlugin: function(target) { + target = $(target); + if (!target.hasClass(this.markerClassName)) { return; } - this._removeTarget(target); - $target.removeClass(this.markerClassName).empty(); - $.removeData(target, PROP_NAME); + this._removeTarget(target[0]); + target.removeClass(this.markerClassName).empty().removeData(this.propertyName); }, /* Pause a countdown widget at the current time. Stop it running but remember and display the current time. @param target (element) the containing division */ - _pauseCountdown: function(target) { + _pausePlugin: function(target) { this._hold(target, 'pause'); }, /* Pause a countdown widget at the current time. Stop the display but keep the countdown running. @param target (element) the containing division */ - _lapCountdown: function(target) { + _lapPlugin: function(target) { this._hold(target, 'lap'); }, /* Resume a paused countdown widget. @param target (element) the containing division */ - _resumeCountdown: function(target) { + _resumePlugin: function(target) { this._hold(target, null); }, @@ -372,7 +392,7 @@ $.extend(Countdown.prototype, { @param target (element) the containing division @param hold (string) the new hold setting */ _hold: function(target, hold) { - var inst = $.data(target, PROP_NAME); + var inst = $.data(target, this.propertyName); if (inst) { if (inst._hold == 'pause' && !hold) { inst._periods = inst._savePeriods; @@ -386,7 +406,7 @@ $.extend(Countdown.prototype, { } inst._hold = hold; inst._savePeriods = (hold == 'pause' ? inst._periods : null); - $.data(target, PROP_NAME, inst); + $.data(target, this.propertyName, inst); this._updateCountdown(target, inst); } }, @@ -394,19 +414,10 @@ $.extend(Countdown.prototype, { /* Return the current time periods. @param target (element) the containing division @return (number[7]) the current periods for the countdown */ - _getTimesCountdown: function(target) { - var inst = $.data(target, PROP_NAME); - return (!inst ? null : (!inst._hold ? inst._periods : - this._calculatePeriods(inst, inst._show, this._get(inst, 'significant'), new Date()))); - }, - - /* Get a setting value, defaulting if necessary. - @param inst (object) the current settings for this instance - @param name (string) the name of the required setting - @return (any) the setting's value or a default if not overridden */ - _get: function(inst, name) { - return (inst.options[name] != null ? - inst.options[name] : $.countdown._defaults[name]); + _getTimesPlugin: function(target) { + var inst = $.data(target, this.propertyName); + return (!inst ? null : (inst._hold == 'pause' ? inst._savePeriods : (!inst._hold ? inst._periods : + this._calculatePeriods(inst, inst._show, inst.options.significant, new Date())))); }, /* A time may be specified as an exact value or a relative one. @@ -440,11 +451,11 @@ $.extend(Countdown.prototype, { case 'w': day += parseInt(matches[1], 10) * 7; break; case 'o': month += parseInt(matches[1], 10); - day = Math.min(day, $.countdown._getDaysInMonth(year, month)); + day = Math.min(day, plugin._getDaysInMonth(year, month)); break; case 'y': year += parseInt(matches[1], 10); - day = Math.min(day, $.countdown._getDaysInMonth(year, month)); + day = Math.min(day, plugin._getDaysInMonth(year, month)); break; } matches = pattern.exec(offset); @@ -477,14 +488,14 @@ $.extend(Countdown.prototype, { @param inst (object) the current settings for this instance @return (string) the new HTML for the countdown display */ _generateHTML: function(inst) { + var self = this; // Determine what to show - var significant = this._get(inst, 'significant'); inst._periods = (inst._hold ? inst._periods : - this._calculatePeriods(inst, inst._show, significant, new Date())); + this._calculatePeriods(inst, inst._show, inst.options.significant, new Date())); // Show all 'asNeeded' after first non-zero value var shownNonZero = false; var showCount = 0; - var sigCount = significant; + var sigCount = inst.options.significant; var show = $.extend({}, inst._show); for (var period = Y; period <= S; period++) { shownNonZero |= (inst._show[period] == '?' && inst._periods[period] > 0); @@ -504,41 +515,40 @@ $.extend(Countdown.prototype, { } } } - var compact = this._get(inst, 'compact'); - var layout = this._get(inst, 'layout'); - var labels = (compact ? this._get(inst, 'compactLabels') : this._get(inst, 'labels')); - var whichLabels = this._get(inst, 'whichLabels') || this._normalLabels; - var timeSeparator = this._get(inst, 'timeSeparator'); - var description = this._get(inst, 'description') || ''; + var labels = (inst.options.compact ? inst.options.compactLabels : inst.options.labels); + var whichLabels = inst.options.whichLabels || this._normalLabels; var showCompact = function(period) { - var labelsNum = $.countdown._get(inst, - 'compactLabels' + whichLabels(inst._periods[period])); - return (show[period] ? inst._periods[period] + + var labelsNum = inst.options['compactLabels' + whichLabels(inst._periods[period])]; + return (show[period] ? self._translateDigits(inst, inst._periods[period]) + (labelsNum ? labelsNum[period] : labels[period]) + ' ' : ''); }; var showFull = function(period) { - var labelsNum = $.countdown._get(inst, 'labels' + whichLabels(inst._periods[period])); - return ((!significant && show[period]) || (significant && showSignificant[period]) ? - '' + - inst._periods[period] + '
' + + var labelsNum = inst.options['labels' + whichLabels(inst._periods[period])]; + return ((!inst.options.significant && show[period]) || + (inst.options.significant && showSignificant[period]) ? + '' + + '' + + self._translateDigits(inst, inst._periods[period]) + '
' + (labelsNum ? labelsNum[period] : labels[period]) + '
' : ''); }; - return (layout ? this._buildLayout(inst, show, layout, compact, significant, showSignificant) : - ((compact ? // Compact version - '' + + return (inst.options.layout ? this._buildLayout(inst, show, inst.options.layout, + inst.options.compact, inst.options.significant, showSignificant) : + ((inst.options.compact ? // Compact version + '' + showCompact(Y) + showCompact(O) + showCompact(W) + showCompact(D) + - (show[H] ? this._minDigits(inst._periods[H], 2) : '') + - (show[M] ? (show[H] ? timeSeparator : '') + - this._minDigits(inst._periods[M], 2) : '') + - (show[S] ? (show[H] || show[M] ? timeSeparator : '') + - this._minDigits(inst._periods[S], 2) : '') : + (show[H] ? this._minDigits(inst, inst._periods[H], 2) : '') + + (show[M] ? (show[H] ? inst.options.timeSeparator : '') + + this._minDigits(inst, inst._periods[M], 2) : '') + + (show[S] ? (show[H] || show[M] ? inst.options.timeSeparator : '') + + this._minDigits(inst, inst._periods[S], 2) : '') : // Full version - '' + + '' + showFull(Y) + showFull(O) + showFull(W) + showFull(D) + showFull(H) + showFull(M) + showFull(S)) + '' + - (description ? '' + description + '' : ''))); + (inst.options.description ? '' + + inst.options.description + '' : ''))); }, /* Construct a custom layout. @@ -550,50 +560,56 @@ $.extend(Countdown.prototype, { @param showSignificant (boolean[7]) other periods to show for significance @return (string) the custom HTML */ _buildLayout: function(inst, show, layout, compact, significant, showSignificant) { - var labels = this._get(inst, (compact ? 'compactLabels' : 'labels')); - var whichLabels = this._get(inst, 'whichLabels') || this._normalLabels; + var labels = inst.options[compact ? 'compactLabels' : 'labels']; + var whichLabels = inst.options.whichLabels || this._normalLabels; var labelFor = function(index) { - return ($.countdown._get(inst, - (compact ? 'compactLabels' : 'labels') + whichLabels(inst._periods[index])) || - labels)[index]; + return (inst.options[(compact ? 'compactLabels' : 'labels') + + whichLabels(inst._periods[index])] || labels)[index]; }; var digit = function(value, position) { - return Math.floor(value / position) % 10; + return inst.options.digits[Math.floor(value / position) % 10]; }; - var subs = {desc: this._get(inst, 'description'), sep: this._get(inst, 'timeSeparator'), - yl: labelFor(Y), yn: inst._periods[Y], ynn: this._minDigits(inst._periods[Y], 2), - ynnn: this._minDigits(inst._periods[Y], 3), y1: digit(inst._periods[Y], 1), + var subs = {desc: inst.options.description, sep: inst.options.timeSeparator, + yl: labelFor(Y), yn: this._minDigits(inst, inst._periods[Y], 1), + ynn: this._minDigits(inst, inst._periods[Y], 2), + ynnn: this._minDigits(inst, inst._periods[Y], 3), y1: digit(inst._periods[Y], 1), y10: digit(inst._periods[Y], 10), y100: digit(inst._periods[Y], 100), y1000: digit(inst._periods[Y], 1000), - ol: labelFor(O), on: inst._periods[O], onn: this._minDigits(inst._periods[O], 2), - onnn: this._minDigits(inst._periods[O], 3), o1: digit(inst._periods[O], 1), + ol: labelFor(O), on: this._minDigits(inst, inst._periods[O], 1), + onn: this._minDigits(inst, inst._periods[O], 2), + onnn: this._minDigits(inst, inst._periods[O], 3), o1: digit(inst._periods[O], 1), o10: digit(inst._periods[O], 10), o100: digit(inst._periods[O], 100), o1000: digit(inst._periods[O], 1000), - wl: labelFor(W), wn: inst._periods[W], wnn: this._minDigits(inst._periods[W], 2), - wnnn: this._minDigits(inst._periods[W], 3), w1: digit(inst._periods[W], 1), + wl: labelFor(W), wn: this._minDigits(inst, inst._periods[W], 1), + wnn: this._minDigits(inst, inst._periods[W], 2), + wnnn: this._minDigits(inst, inst._periods[W], 3), w1: digit(inst._periods[W], 1), w10: digit(inst._periods[W], 10), w100: digit(inst._periods[W], 100), w1000: digit(inst._periods[W], 1000), - dl: labelFor(D), dn: inst._periods[D], dnn: this._minDigits(inst._periods[D], 2), - dnnn: this._minDigits(inst._periods[D], 3), d1: digit(inst._periods[D], 1), + dl: labelFor(D), dn: this._minDigits(inst, inst._periods[D], 1), + dnn: this._minDigits(inst, inst._periods[D], 2), + dnnn: this._minDigits(inst, inst._periods[D], 3), d1: digit(inst._periods[D], 1), d10: digit(inst._periods[D], 10), d100: digit(inst._periods[D], 100), d1000: digit(inst._periods[D], 1000), - hl: labelFor(H), hn: inst._periods[H], hnn: this._minDigits(inst._periods[H], 2), - hnnn: this._minDigits(inst._periods[H], 3), h1: digit(inst._periods[H], 1), + hl: labelFor(H), hn: this._minDigits(inst, inst._periods[H], 1), + hnn: this._minDigits(inst, inst._periods[H], 2), + hnnn: this._minDigits(inst, inst._periods[H], 3), h1: digit(inst._periods[H], 1), h10: digit(inst._periods[H], 10), h100: digit(inst._periods[H], 100), h1000: digit(inst._periods[H], 1000), - ml: labelFor(M), mn: inst._periods[M], mnn: this._minDigits(inst._periods[M], 2), - mnnn: this._minDigits(inst._periods[M], 3), m1: digit(inst._periods[M], 1), + ml: labelFor(M), mn: this._minDigits(inst, inst._periods[M], 1), + mnn: this._minDigits(inst, inst._periods[M], 2), + mnnn: this._minDigits(inst, inst._periods[M], 3), m1: digit(inst._periods[M], 1), m10: digit(inst._periods[M], 10), m100: digit(inst._periods[M], 100), m1000: digit(inst._periods[M], 1000), - sl: labelFor(S), sn: inst._periods[S], snn: this._minDigits(inst._periods[S], 2), - snnn: this._minDigits(inst._periods[S], 3), s1: digit(inst._periods[S], 1), + sl: labelFor(S), sn: this._minDigits(inst, inst._periods[S], 1), + snn: this._minDigits(inst, inst._periods[S], 2), + snnn: this._minDigits(inst, inst._periods[S], 3), s1: digit(inst._periods[S], 1), s10: digit(inst._periods[S], 10), s100: digit(inst._periods[S], 100), s1000: digit(inst._periods[S], 1000)}; var html = layout; // Replace period containers: {p<}...{p>} for (var i = Y; i <= S; i++) { var period = 'yowdhms'.charAt(i); - var re = new RegExp('\\{' + period + '<\\}(.*)\\{' + period + '>\\}', 'g'); + var re = new RegExp('\\{' + period + '<\\}([\\s\\S]*)\\{' + period + '>\\}', 'g'); html = html.replace(re, ((!significant && show[i]) || (significant && showSignificant[i]) ? '$1' : '')); } @@ -606,16 +622,27 @@ $.extend(Countdown.prototype, { }, /* Ensure a numeric value has at least n digits for display. + @param inst (object) the current settings for this instance @param value (number) the value to display @param len (number) the minimum length @return (string) the display text */ - _minDigits: function(value, len) { + _minDigits: function(inst, value, len) { value = '' + value; if (value.length >= len) { - return value; + return this._translateDigits(inst, value); } value = '0000000000' + value; - return value.substr(value.length - len); + return this._translateDigits(inst, value.substr(value.length - len)); + }, + + /* Translate digits into other representations. + @param inst (object) the current settings for this instance + @param value (string) the text to translate + @return (string) the translated text */ + _translateDigits: function(inst, value) { + return ('' + value).replace(/[0-9]/g, function(digit) { + return inst.options.digits[digit]; + }); }, /* Translate the format into flags for each period. @@ -623,7 +650,7 @@ $.extend(Countdown.prototype, { @return (string[7]) flags indicating which periods are requested (?) or required (!) by year, month, week, day, hour, minute, second */ _determineShow: function(inst) { - var format = this._get(inst, 'format'); + var format = inst.options.format; var show = []; show[Y] = (format.match('y') ? '?' : (format.match('Y') ? '!' : null)); show[O] = (format.match('o') ? '?' : (format.match('O') ? '!' : null)); @@ -665,8 +692,8 @@ $.extend(Countdown.prototype, { var periods = [0, 0, 0, 0, 0, 0, 0]; if (show[Y] || show[O]) { // Treat end of months as the same - var lastNow = $.countdown._getDaysInMonth(now.getFullYear(), now.getMonth()); - var lastUntil = $.countdown._getDaysInMonth(until.getFullYear(), until.getMonth()); + var lastNow = plugin._getDaysInMonth(now.getFullYear(), now.getMonth()); + var lastUntil = plugin._getDaysInMonth(until.getFullYear(), until.getMonth()); var sameDay = (until.getDate() == now.getDate() || (until.getDate() >= Math.min(lastNow, lastUntil) && now.getDate() >= Math.min(lastNow, lastUntil))); @@ -682,7 +709,7 @@ $.extend(Countdown.prototype, { // Adjust for months difference and end of month if necessary now = new Date(now.getTime()); var wasLastDay = (now.getDate() == lastNow); - var lastDay = $.countdown._getDaysInMonth(now.getFullYear() + periods[Y], + var lastDay = plugin._getDaysInMonth(now.getFullYear() + periods[Y], now.getMonth() + periods[O]); if (now.getDate() > lastDay) { now.setDate(lastDay); @@ -737,41 +764,47 @@ $.extend(Countdown.prototype, { } }); -/* jQuery extend now ignores nulls! - @param target (object) the object to update - @param props (object) the new settings - @return (object) the updated object */ -function extendRemove(target, props) { - $.extend(target, props); - for (var name in props) { - if (props[name] == null) { - target[name] = null; - } +// The list of commands that return values and don't permit chaining +var getters = ['getTimes']; + +/* Determine whether a command is a getter and doesn't permit chaining. + @param command (string, optional) the command to run + @param otherArgs ([], optional) any other arguments for the command + @return true if the command is a getter, false if not */ +function isNotChained(command, otherArgs) { + if (command == 'option' && (otherArgs.length == 0 || + (otherArgs.length == 1 && typeof otherArgs[0] == 'string'))) { + return true; } - return target; + return $.inArray(command, getters) > -1; } /* Process the countdown functionality for a jQuery selection. - @param command (string) the command to run (optional, default 'attach') - @param options (object) the new settings to use for these countdown instances - @return (jQuery) for chaining further calls */ + @param options (object) the new settings to use for these instances (optional) or + (string) the command to run (optional) + @return (jQuery) for chaining further calls or + (any) getter value */ $.fn.countdown = function(options) { var otherArgs = Array.prototype.slice.call(arguments, 1); - if (options == 'getTimes' || options == 'settings') { - return $.countdown['_' + options + 'Countdown']. - apply($.countdown, [this[0]].concat(otherArgs)); + if (isNotChained(options, otherArgs)) { + return plugin['_' + options + 'Plugin']. + apply(plugin, [this[0]].concat(otherArgs)); } return this.each(function() { if (typeof options == 'string') { - $.countdown['_' + options + 'Countdown'].apply($.countdown, [this].concat(otherArgs)); + if (!plugin['_' + options + 'Plugin']) { + throw 'Unknown command: ' + options; + } + plugin['_' + options + 'Plugin']. + apply(plugin, [this].concat(otherArgs)); } else { - $.countdown._attachCountdown(this, options); + plugin._attachPlugin(this, options || {}); } }); }; /* Initialise the countdown functionality. */ -$.countdown = new Countdown(); // singleton instance +var plugin = $.countdown = new Countdown(); // Singleton instance })(jQuery);