(function($) {
$.fn.countdown = function( method /*, options*/ ) {
var msPerHr = 3600000,
secPerYear = 31557600,
secPerMonth = 2629800,
secPerWeek = 604800,
secPerDay = 86400,
secPerHr = 3600,
secPerMin = 60,
secPerSec = 1,
rTemplateTokens = /%y|%m|%w|%d|%h|%i|%s/g,
rDigitGlobal = /\d/g,
localNumber = function( numToConvert, settings ) {
var arr = numToConvert.toString().match(rDigitGlobal),
localeNumber = "";
$.each( arr, function(i,num) {
num = Number(num);
localeNumber += (""+ settings.digits[num]) || ""+num;
});
return localeNumber;
},
generateTemplateCustom = function( settings ) {
var template = settings.template,
$parent = $('
'),
$timeWrapElement = settings.dom.$time.addClass( settings.timeWrapClass ),
$textWrapElement = $("<"+settings.textWrapElement+">").addClass( settings.textWrapClass ),
sep = settings.timeSeparator,
yearsLeft = settings.yearsLeft,
monthsLeft = settings.monthsLeft,
weeksLeft = settings.weeksLeft,
daysLeft = settings.daysLeft,
hrsLeft = settings.hrsLeft,
minsLeft = settings.minsLeft,
secLeft = settings.secLeft,
hideYears = false,
hideMonths = false,
hideWeeks = false,
hideDays = false,
hideHours = false,
hideMins = false,
hideSecs = false,
timeTasks = [],
formatTime = function(time, text, showSeparator) {
return '
'+time+''+text+'';
};
if( settings.omitZero ) {
if( settings.yearsAndMonths ) {
if( !settings.yearsLeft ) {
hideYears = true;
}
if( !settings.monthsLeft ) {
hideMonths = true;
}
}
if( settings.weeks && ( ( settings.yearsAndMonths && hideMonths && !settings.weeksLeft ) || ( !settings.yearsAndMonths && !settings.weeksLeft ) ) ) {
hideWeeks = true;
}
if( hideWeeks && !daysLeft ) {
hideDays = true;
}
if( hideDays && !hrsLeft ) {
hideHours = true;
}
if( hideHours && !minsLeft ) {
hideMins = true;
}
}
if( settings.leadingZero ) {
if( yearsLeft < 10 ) {
yearsLeft = "0" + yearsLeft;
}
if( monthsLeft < 10 ) {
monthsLeft = "0" + monthsLeft;
}
if( weeksLeft < 10 ) {
weeksLeft = "0" + weeksLeft;
}
if( daysLeft < 10 ) {
daysLeft = "0" + daysLeft;
}
if( hrsLeft < 10 ) {
hrsLeft = "0" + hrsLeft;
}
if( minsLeft < 10 ) {
minsLeft = "0" + minsLeft;
}
if( secLeft < 10 ) {
secLeft = "0" + secLeft;
}
}
yearsLeft = localNumber( yearsLeft, settings );
monthsLeft = localNumber( monthsLeft, settings );
weeksLeft = localNumber( weeksLeft, settings );
daysLeft = localNumber( daysLeft, settings );
hrsLeft = localNumber( hrsLeft, settings );
minsLeft = localNumber( minsLeft, settings );
secLeft = localNumber( secLeft, settings );
if( settings.yearsAndMonths ) {
if( !settings.omitZero || !hideYears ) {
template = template.replace('%y', formatTime(yearsLeft, (yearsLeft == 1 && settings.yearSingularText) ? settings.yearSingularText : settings.yearText, true));
}
//Only hide months if years is at 0 as well as months
if( !settings.omitZero || ( !hideYears && monthsLeft ) || ( !hideYears && !hideMonths ) ) {
template = template.replace('%m', formatTime(monthsLeft, (monthsLeft == 1 && settings.monthSingularText) ? settings.monthSingularText : settings.monthText, true));
} else {
template = template.replace('%m', '');
}
}
if( settings.weeks && !hideWeeks ) {
template = template.replace('%w', formatTime(weeksLeft, (weeksLeft == 1 && settings.weekSingularText) ? settings.weekSingularText : settings.weekText, true));
} else {
template = template.replace('%w', '');
}
if( !hideDays ) {
template = template.replace('%d', formatTime(daysLeft, (daysLeft == 1 && settings.daySingularText) ? settings.daySingularText : settings.dayText, true));
} else {
template = template.replace('%d', '');
}
if( !hideHours ) {
template = template.replace('%h', formatTime(hrsLeft, (hrsLeft == 1 && settings.hourSingularText) ? settings.hourSingularText : settings.hourText, true));
} else {
template = template.replace('%h', '');
}
if( !hideMins ) {
template = template.replace('%i', formatTime(minsLeft, (minsLeft == 1 && settings.minSingularText) ? settings.minSingularText : settings.minText, true));
} else {
template = template.replace('%i', '');
}
template = template.replace('%s', formatTime(secLeft, (secLeft == 1 && settings.secSingularText) ? settings.secSingularText : settings.secText));
// Remove un-used tokens
template = template.replace(rTemplateTokens,'');
return template;
},
generateTemplate = function( settings ) {
var $parent = $('
'),
$timeWrapElement = $("<"+settings.timeWrapElement+">").addClass( settings.timeWrapClass ),
$textWrapElement = $("<"+settings.textWrapElement+">").addClass( settings.textWrapClass ),
sep = settings.timeSeparator,
yearsLeft = settings.yearsLeft,
monthsLeft = settings.monthsLeft,
weeksLeft = settings.weeksLeft,
daysLeft = settings.daysLeft,
hrsLeft = settings.hrsLeft,
minsLeft = settings.minsLeft,
secLeft = settings.secLeft,
template = settings.template,
hasTemplate = !!settings.template,
hideYears = false,
hideMonths = false,
hideWeeks = false,
hideDays = false,
hideHours = false,
hideMins = false,
hideSecs = false,
timeTasks = [],
addTime = function( time ) {
timeTasks.push(function() {
$parent.append( $timeWrapElement.clone().html( time + settings.spaceCharacter ) );
});
},
addText = function( text ) {
timeTasks.push(function() {
$parent.append( $textWrapElement.clone().html( text + settings.spaceCharacter ) );
});
},
addSeparator = function() {
timeTasks.push(function() {
$parent.append( $textWrapElement.clone().html( settings.spaceCharacter + sep + settings.spaceCharacter) );
});
},
formatTime = function(time, text, showSeparator) {
return ''+time+''+text+'';
};
if( settings.template )
{
return generateTemplateCustom( settings );
}
if( settings.omitZero ) {
if( settings.yearsAndMonths ) {
if( !settings.yearsLeft ) {
hideYears = true;
}
if( !settings.monthsLeft ) {
hideMonths = true;
}
}
if( settings.weeks && ( ( settings.yearsAndMonths && hideMonths && !settings.weeksLeft ) || ( !settings.yearsAndMonths && !settings.weeksLeft ) ) ) {
hideWeeks = true;
}
if( hideWeeks && !daysLeft ) {
hideDays = true;
}
if( hideDays && !hrsLeft ) {
hideHours = true;
}
if( hideHours && !minsLeft ) {
hideMins = true;
}
}
if( settings.leadingZero ) {
if( yearsLeft < 10 ) {
yearsLeft = "0" + yearsLeft;
}
if( monthsLeft < 10 ) {
monthsLeft = "0" + monthsLeft;
}
if( weeksLeft < 10 ) {
weeksLeft = "0" + weeksLeft;
}
if( daysLeft < 10 ) {
daysLeft = "0" + daysLeft;
}
if( hrsLeft < 10 ) {
hrsLeft = "0" + hrsLeft;
}
if( minsLeft < 10 ) {
minsLeft = "0" + minsLeft;
}
if( secLeft < 10 ) {
secLeft = "0" + secLeft;
}
}
yearsLeft = localNumber( yearsLeft, settings );
monthsLeft = localNumber( monthsLeft, settings );
weeksLeft = localNumber( weeksLeft, settings );
daysLeft = localNumber( daysLeft, settings );
hrsLeft = localNumber( hrsLeft, settings );
minsLeft = localNumber( minsLeft, settings );
secLeft = localNumber( secLeft, settings );
if( settings.yearsAndMonths ) {
if( !settings.omitZero || !hideYears ) {
addTime( yearsLeft );
addText( (yearsLeft == 1 && settings.yearSingularText) ? settings.yearSingularText : settings.yearText );
addSeparator();
}
//Only hide months if years is at 0 as well as months
if( !settings.omitZero || ( !hideYears && monthsLeft ) || ( !hideYears && !hideMonths ) ) {
addTime( monthsLeft );
addText( (monthsLeft == 1 && settings.monthSingularText) ? settings.monthSingularText : settings.monthText );
addSeparator();
}
}
if( settings.weeks && !hideWeeks ) {
addTime( weeksLeft );
addText( (weeksLeft == 1 && settings.weekSingularText) ? settings.weekSingularText : settings.weekText );
addSeparator();
}
if( !hideDays ) {
addTime( daysLeft );
addText( (daysLeft == 1 && settings.daySingularText) ? settings.daySingularText : settings.dayText );
addSeparator();
}
if( !hideHours ) {
addTime( hrsLeft );
addText( (hrsLeft == 1 && settings.hourSingularText) ? settings.hourSingularText : settings.hourText );
addSeparator();
}
if( !hideMins ) {
addTime( minsLeft );
addText( (minsLeft == 1 && settings.minSingularText) ? settings.minSingularText : settings.minText );
addSeparator();
}
addTime( secLeft );
addText( (secLeft == 1 && settings.secSingularText) ? settings.secSingularText : settings.secText );
if( settings.isRTL === true ) {
timeTasks.reverse();
}
$.each( timeTasks, function(i,task ) {
task();
});
template = $parent.html();
return template;
},
dateNow = function( $this ) {
var now = new Date(), //Default to local time
settings = $this.data("jcdData");
if( !settings ) {
return new Date();
}
if( settings.offset !== null ) {
now = getTZDate( settings.offset );
}
now.setMilliseconds(0);
return now;
},
getTZDate = function( offset ) {
// Returns date now based on timezone/offset
var hrs,
dateMS,
curHrs,
tmpDate = new Date();
if( offset !== null ) {
hrs = offset * msPerHr;
curHrs = tmpDate.getTime() - ( ( -tmpDate.getTimezoneOffset() / 60 ) * msPerHr ) + hrs;
dateMS = tmpDate.setTime( curHrs );
}
return new Date( dateMS );
},
timerFunc = function() {
//Function runs at set interval updating countdown
var $this = this,
template,
now,
date,
timeLeft,
yearsLeft = 0,
monthsLeft = 0,
weeksLeft = 0,
daysLeft = 0,
hrsLeft = 0,
minsLeft = 0,
secLeft = 0,
time = "",
diff,
hideYears = false,
hideMonths = false,
hideWeeks = false,
hideDays = false,
hideHours = false,
hideMins = false,
hideSecs = false,
extractSection = function( numSecs ) {
var amount;
amount = Math.floor( diff / numSecs );
diff -= amount * numSecs;
return amount;
},
settings = $this.data("jcdData");
if( !settings ) {
return false;
}
template = settings.htmlTemplate;
now = dateNow( $this );
if( settings.serverDiff !== null ) {
date = new Date( settings.serverDiff + settings.clientdateNow.getTime() );
} else {
date = settings.dateObj; //Date to countdown to
}
date.setMilliseconds(0);
timeLeft = ( settings.direction === "down" ) ? date.getTime() - now.getTime() : now.getTime() - date.getTime();
diff = Math.round( timeLeft / 1000 );
daysLeft = extractSection( secPerDay );
hrsLeft = extractSection( secPerHr );
minsLeft = extractSection( secPerMin );
secLeft = extractSection( secPerSec );
if( settings.yearsAndMonths ) {
//Add days back on so we can calculate years easier
diff += ( daysLeft * secPerDay );
yearsLeft = extractSection( secPerYear );
monthsLeft = extractSection( secPerMonth );
daysLeft = extractSection( secPerDay );
}
if( settings.weeks ) {
//Add days back on so we can calculate weeks easier
diff += ( daysLeft * secPerDay );
weeksLeft = extractSection( secPerWeek );
daysLeft = extractSection( secPerDay );
}
/**
* The following 3 option should never be used together!
* MAKE them work for any time
*/
if( settings.hoursOnly || settings.minsOnly || settings.secsOnly )
{
if( settings.yearsAndMonths ) {
//Add years, months, weeks and days back on so we can calculate dates easier
diff += ( yearsLeft * secPerYear );
diff += ( monthsLeft * secPerMonth );
yearsLeft = monthsLeft = 0;
}
if( settings.weeks ) {
diff += ( weeksLeft * secPerWeek );
weeksLeft = 0;
}
}
//Assumes you are using dates within a month ( ~ 30 days )
//as years and months aren't taken into account
if( settings.hoursOnly ) {
// Add days back on
diff += ( daysLeft * secPerDay );
// Add hours back on
diff += ( hrsLeft * secPerHr );
hrsLeft = extractSection( secPerHr );
}
//Assumes you are only using dates in the near future ( <= 24 hours )
//as years and months aren't taken into account
if( settings.minsOnly ) {
// Add days back on
diff += ( daysLeft * secPerDay );
daysLeft = 0;
// Add hours back on
diff += ( hrsLeft * secPerHr );
hrsLeft = 0;
diff += ( minsLeft * secPerMin );
minsLeft = extractSection( secPerMin );
}
//Assumes you are only using dates in the near future ( <= 60 minutes )
//as years, months and days aren't taken into account
if( settings.secsOnly ) {
// Add days back on
diff += ( daysLeft * secPerDay );
daysLeft = 0;
// Add hours back on
diff += ( hrsLeft * secPerHr );
hrsLeft = 0;
// Add minutes back on
diff += ( minsLeft * secPerMin );
minsLeft = 0;
// Add seconds back on
diff += secLeft;
secLeft = extractSection( secPerSec );
}
settings.yearsLeft = yearsLeft;
settings.monthsLeft = monthsLeft;
settings.weeksLeft = weeksLeft;
settings.daysLeft = daysLeft;
settings.hrsLeft = hrsLeft;
settings.minsLeft = minsLeft;
settings.secLeft = secLeft;
$this.data("jcdData", settings);
if ( ( settings.direction === "down" && ( now < date || settings.minus ) ) || ( settings.direction === "up" && ( date < now || settings.minus ) ) ) {
time = generateTemplate( settings );
} else {
settings.yearsLeft = settings.monthsLeft = settings.weeksLeft = settings.daysLeft = settings.hrsLeft = settings.minsLeft = settings.secLeft = 0;
time = generateTemplate( settings );
settings.hasCompleted = true;
}
$this.html( time ).triggerMulti("change.jcdevt,countChange", [settings]);
if ( settings.hasCompleted ) {
$this.triggerMulti("complete.jcdevt,countComplete");
clearInterval( settings.timer );
}
},
methods = {
init: function( options ) {
var opts = $.extend( {}, $.fn.countdown.defaults, options ),
local = null,
testDate,
testString;
return this.each(function() {
var $this = $(this),
settings = {},
func;
//If this element already has a countdown timer, just change the settings
if( $this.data("jcdData") ) {
$this.countdown("changeSettings", options, true);
opts = $this.data("jcdData");
}
if( opts.date === null && opts.dataAttr === null ) {
$.error("No Date passed to jCountdown. date option is required.");
return true;
}
if( opts.date ) {
testString = opts.date;
} else {
testString = $this.data(opts.dataAttr);
}
testDate = new Date(testString);
if( testDate.toString() === "Invalid Date" ) {
$.error("Invalid Date passed to jCountdown: " + testString);
}
//Add event handlers where set
if( opts.onStart ) {
$this.on("start.jcdevt", opts.onStart );
}
if( opts.onChange ) {
$this.on("change.jcdevt", opts.onChange );
}
if( opts.onComplete ) {
$this.on("complete.jcdevt", opts.onComplete );
}
if( opts.onPause ) {
$this.on("pause.jcdevt", opts.onPause );
}
if( opts.onResume ) {
$this.on("resume.jcdevt", opts.onResume );
}
if( opts.onLocaleChange ) {
$this.on("locale.jcdevt", opts.onLocaleChange );
}
settings = $.extend( {}, opts );
// Cache DOM elements for templating
settings.dom = {};
settings.dom.$time = $("<"+settings.timeWrapElement+">").addClass( settings.timeWrapClass );
settings.dom.$text = $("<"+settings.textWrapElement+">").addClass( settings.textWrapClass );
settings.clientdateNow = new Date();
settings.clientdateNow.setMilliseconds(0);
settings.originalHTML = $this.html();
settings.dateObj = new Date( testString );
settings.dateObj.setMilliseconds(0);
settings.hasCompleted = false;
settings.timer = 0;
settings.yearsLeft = settings.monthsLeft = settings.weeksLeft = settings.daysLeft = settings.hrsLeft = settings.minsLeft = settings.secLeft = 0;
settings.difference = null;
func = $.proxy( timerFunc, $this );
settings.timer = setInterval( func, settings.updateTime );
$this.data("jcdData", settings).triggerMulti("start.jcdevt,countStart", [settings]);
func();
});
},
changeSettings: function( options, internal ) {
//Like resume but with resetting/changing options
return this.each(function() {
var $this = $(this),
settings,
testDate,
func = $.proxy( timerFunc, $this );
if( !$this.data("jcdData") ) {
return true;
}
settings = $.extend( {}, $this.data("jcdData"), options );
if( options.hasOwnProperty("date") ) {
testDate = new Date(options.date);
if( testDate.toString() === "Invalid Date" ) {
$.error("Invalid Date passed to jCountdown: " + options.date);
}
}
settings.completed = false;
settings.dateObj = new Date( options.date );
//Clear the timer, as it might not be needed
clearInterval( settings.timer );
$this.off(".jcdevt").data("jcdData", settings);
//As this can be accessed via the init method as well,
//we need to check how this method is being accessed
if( !internal ) {
if( settings.onChange ) {
$this.on("change.jcdevt", settings.onChange);
}
if( settings.onComplete ) {
$this.on("complete.jcdevt", settings.onComplete);
}
if( settings.onPause ) {
$this.on("pause.jcdevt", settings.onPause );
}
if( settings.onResume ) {
$this.on("resume.jcdevt", settings.onResume );
}
settings.timer = setInterval( func, settings.updateTime );
$this.data("jcdData", settings);
func(); //Needs to run straight away when changing settings
}
settings = null;
});
},
resume: function() {
//Resumes a countdown timer
return this.each(function() {
var $this = $(this),
settings = $this.data("jcdData"),
func = $.proxy( timerFunc, $this );
if( !settings ) {
return true;
}
$this.data("jcdData", settings).triggerMulti("resume.jcdevt,countResume", [settings] );
//We only want to resume a countdown that hasn't finished
if( !settings.hasCompleted ) {
settings.timer = setInterval( func, settings.updateTime );
if( settings.stopwatch && settings.direction === "up" ) {
var t = dateNow( $this ).getTime() - settings.pausedAt.getTime(),
d = new Date();
d.setTime( settings.dateObj.getTime() + t );
settings.dateObj = d; //This is internal date
}
func();
}
});
},
pause: function() {
//Pause a countdown timer
return this.each(function() {
var $this = $(this),
settings = $this.data("jcdData");
if( !settings ) {
return true;
}
if( settings.stopwatch ) {
settings.pausedAt = dateNow( $this );
}
//Clear interval (Will be started on resume)
clearInterval( settings.timer );
//Trigger pause event handler
$this.data("jcdData", settings).triggerMulti("pause.jcdevt,countPause", [settings] );
});
},
complete: function() {
return this.each(function() {
var $this = $(this),
settings = $this.data("jcdData");
if( !settings ) {
return true;
}
//Clear timer
clearInterval( settings.timer );
settings.hasCompleted = true;
//Update setting, trigger complete event handler, then unbind all events
//We don"t delete the settings in case they need to be checked later on
$this.data("jcdData", settings).triggerMulti("complete.jcdevt,countComplete", [settings]);
//$this.off(".jcdevt, co");
});
},
destroy: function() {
return this.each(function() {
var $this = $(this),
settings = $this.data("jcdData");
if( !settings ) {
return true;
}
//Clear timer
clearInterval( settings.timer );
//Unbind all events, remove data and put DOM Element back to its original state (HTML wise)
$this.off(".jcdevt").removeData("jcdData").html( settings.originalHTML );
});
},
getSettings: function( name ) {
var $this = $(this),
settings = $this.data("jcdData");
//If an individual setting is required
if( name && settings ) {
//If it exists, return it
if( settings.hasOwnProperty( name ) ) {
return settings[name];
}
return undefined;
}
//Return all settings or undefined
return settings;
},
changeLocale: function( locale ) { //new in v1.5.0
var $this = $(this),
settings = $this.data("jcdData");
// If no locale exists error and return false
if( !$.fn.countdown.locale[locale] ) {
$.error("Locale '" + locale + "' does not exist");
return false;
}
$.extend( settings, $.fn.countdown.locale[locale] );
$this.data("jcdData", settings).triggerMulti("locale.jcdevt,localeChange", [settings]);
return true;
}
};
if( methods[ method ] ) {
return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ) );
} else if ( typeof method === "object" || !method ) {
return methods.init.apply( this, arguments );
} else {
$.error("Method "+ method +" does not exist in the jCountdown Plugin");
}
};
// new in v1.5.0
$.fn.countdown.defaults = {
date: null,
dataAttr: null,
updateTime: 1000,
yearText: 'years',
monthText: 'months',
weekText: 'weeks',
dayText: 'days',
hourText: 'hours',
minText: 'mins',
secText: 'sec',
yearSingularText: 'year',
monthSingularText: 'month',
weekSingularText: 'week',
daySingularText: 'day',
hourSingularText: 'hour',
minSingularText: 'min',
secSingularText: 'sec',
digits : [0,1,2,3,4,5,6,7,8,9],
timeWrapElement: 'span',
textWrapElement: 'span',
timeWrapClass: '',
textWrapClass: 'cd-time',
timeSeparator: '',
isRTL: false,
minus: false,
onStart: null,
onChange: null,
onComplete: null,
onResume: null,
onPause: null,
onLocaleChange: null,
leadingZero: false,
offset: null,
serverDiff:null,
spaceCharacter: ' ',
hoursOnly: false,
minsOnly: false,
secsOnly: false,
weeks: false,
hours: false,
yearsAndMonths: false,
direction: "down",
stopwatch: false,
omitZero: false,
template: null
};
//Create an array to store new locales
// new in v1.5.0
$.fn.countdown.locale = [];
// new in v1.5.0
$.fn.triggerMulti = function( eventTypes, extraParameters ) {
var events = eventTypes.split(",");
return this.each(function() {
var $this = $(this);
for( var i = 0; i < events.length; i++) {
$this.trigger( events[i], extraParameters );
}
});
};
})(jQuery);