/** * jQuery Countdown * * Provides a javascript countdown timer based on the data-countdown attribute or by * passing a schedule of events via options. It will default to data-countdown if the * attribute is present, then the next day in a schedule if you pass one, and * then it will fail silently as last result if neither of these are present. * * @author Kaleb Heitzman * @website http://github.com/kaleblex/jquery-countdown.js * @created March 12, 2013 * */ ; (function($) { /** * Countdown */ $.fn.countdown = function(options) { // default options defaults = { schedule: null, datetime: null, showYears: false, showDays: true, showHours: true, showMinutes: true, showSeconds: true, showOnZeroYears: false, showOnZeroDays: true, showOnZeroHours: true, showOnZeroMinutes: true, showOnZeroSeconds: true, unixFormat: false }; /** * Extend the options */ var options = $.extend({ timerCallback: function(options) {}, initCallback: function(options) {}, zeroCallback: function(options) {} }, defaults, options); /** * Check for a specified datetime and use ternary to apply value */ options.datetime = $(this).attr('data-countdown') ? $(this).attr('data-countdown') : null; /** * Run Countdown on Element */ return this.each(function() { /** * Calculate Upcoming Day * * Calculate what the upcoming day is based on a combination of schedules * and date-time attribute. If a date-time attribute is used, it should be * used first. If a schedule is passed, calculate the date and time based * on the schedule */ var upcomingDate = scheduler(options); /** * Get the element to update */ var element = $(this); /** * Start the timer * * The time will update the element every one second. */ var intervalHandle = setInterval(function() { // get the timerObject var timerObject = makeTimer(upcomingDate, options); // set the timerObject in options options.timerObject = timerObject; // create the HTML var timerHtml = htmlParser(timerObject, options); /** * Update the HTML for the counter */ updateElement(element, timerHtml, options); /** * Check for zeroCallback */ if (timerObject.timeLeft <= 0) { /** * Clear the interval because it's no longer needed */ clearInterval(intervalHandle); /** * Call the zeroCallback to see if there is anything to perform. */ options.zeroCallback(options); } /** * call the callback timer */ else { options.timerCallback(options); } }, 1000); }); /** * An overball callback */ options.initCallback.call(this); }; /** * Update the element * * Updates the html inside of the element that plugin is attached to based on * schedule or date-time attr. */ var updateElement = function(element, html, options) { // update the html $(element).html(html); } /** * Scheduler * * Returns Human Readable date string based on a combination of scheduel, * date-time attr, and current datetime. */ var scheduler = function(options) { /** * Check for registerd data-countdown attr. * * If the date-time attr is filled in, skip schedule checking and return * the passed data-countdown directly. */ if (options.datetime != null) { return options.datetime; } /** * Set a blank array to store date and time sin */ var upcomingDates = []; /** * Process the schedule */ for (var day in options.schedule) { // get the next upcoming date var nextDate = nextDayByName(day); // get the time foreach each future date and push it to upcomingDates for (var time in options.schedule[day]) { // create a time string to push onto the upcomingDates array var timeString = nextDate + " " + options.schedule[day][time]; // push the string onto the array upcomingDates.push(timeString); } } /** * Create the schedule to compare times agains */ schedule = []; for (var key in upcomingDates) { schedule.push(new Date(upcomingDates[key])); } /** * Create the parsed schedule */ parsedSchedule = []; for (var key in schedule) { parsedSchedule.push(Date.parse(schedule[key]) / 1000); } /** * Current Time */ var currentTime = new Date(), currentTimeParsed = Date.parse(currentTime) / 1000; /** * Build time differences */ timeDifferences = []; for (var key in parsedSchedule) { timeDifferences.push(parsedSchedule[key] - currentTimeParsed); } /** * Check for Negative */ timeDifferencesParsed = []; for (key in timeDifferences) { if (timeDifferences[key] > 0) { timeDifferencesParsed.push(timeDifferences[key]); } } /** * Get the shortest time and store it in key */ var shortTime = Math.min.apply(null, timeDifferencesParsed); for (var prop in timeDifferences) { if (shortTime == timeDifferences[prop]) { var shortTimeKey = prop; } } /** * Get the next scheduled date */ var scheduledDate = upcomingDates[shortTimeKey]; /** * Return the scheduledDate if it's not blank. */ if (scheduledDate != "") { return scheduledDate; } /** * When all else fails return null */ return null; } /** * Make Timer * * This is the timing method. This will be called every 1 second to return * the html needed to be placed in $(this.element) */ var makeTimer = function(upcomingDate, options) { /** * Check for null upcomingDate */ if (upcomingDate == null) { return ""; } /** * Current Time and Date */ var currentTime = new Date(); /** * Convert Dates * * Convert the dates to a time string to calculate differences. */ if (options.unixFormat) { var endTime = (upcomingDate / 1000); } else { var endTime = (Date.parse(upcomingDate) / 1000); } var currentTime = (Date.parse(currentTime) / 1000); /** * Time Left */ var timeLeft = endTime - currentTime; /** * Set some vars for use */ var years = 0; var days = 0; var hours = 0; var minutes = 0; var seconds = 0; /** * Intercept on <= 0 and calculate differences based on timeLeft */ if (timeLeft > 0) { var years = Math.floor((timeLeft / 31536000)); var days = Math.floor((timeLeft / 86400)); var hours = Math.floor((timeLeft - (days * 86400)) / 3600); var minutes = Math.floor((timeLeft - (days * 86400) - (hours * 3600)) / 60); var seconds = Math.floor((timeLeft - (days * 86400) - (hours * 3600) - (minutes * 60))); // adjust days for for more than 1 year if (days > 365) { days = days % 365; } } // create the Timer Object var timerObject = { "years": years, "days": days, "hours": hours, "minutes": minutes, "seconds": seconds, "timeLeft": timeLeft }; // return Timer Object return timerObject; } /** * Parses a Timer Object to create html */ var htmlParser = function(timerObject, options, format) { /** * Adjust for zero */ if (timerObject.years < "10") { timerObject.years = "0" + timerObject.years; } if (timerObject.days < "10") { timerObject.days = "0" + timerObject.days; } if (timerObject.hours < "10") { timerObject.hours = "0" + timerObject.hours; } if (timerObject.minutes < "10") { timerObject.minutes = "0" + timerObject.minutes; } if (timerObject.seconds < "10") { timerObject.seconds = "0" + timerObject.seconds; } /** * Counter HTML to be passed back to the element */ var counter_years = '
' + timerObject.years + 'Years
'; var counter_days = '
' + timerObject.days + 'Days
'; var counter_hours = '
' + timerObject.hours + 'Hours
'; var counter_minutes = '
' + timerObject.minutes + 'Minutes
'; var counter_seconds = '
' + timerObject.seconds + 'Seconds
'; /** * Setup string inclusions */ var includeYears = false, includeDays = false, includeHours = false, includeMinutes = false, includeSeconds = false; /** * Options base show logic */ if (options.showYears) { includeYears = true; } if (options.showDays) { includeDays = true; } if (options.showHours) { includeHours = true; } if (options.showMinutes) { includeMinutes = true; } if (options.showSeconds) { includeSeconds = true; } /** * Options showOnZero logic */ if ((!options.showOnZeroYears) && (timerObject.years == "00")) { includeYears = false; } if ((!options.showOnZeroDays) && (timerObject.days == "00")) { includeDays = false; } if ((!options.showOnZeroHours) && (timerObject.hours == "00")) { includeHours = false; } if ((!options.showOnZeroMinutes) && (timerObject.minutes == "00")) { includeMinutes = false; } if ((!options.showOnZeroSeconds) && (timerObject.seconds == "00")) { includeSeconds = false; } /** Concatonate string */ var counter_html = ""; if (includeYears) { counter_html += counter_years; } if (includeDays) { counter_html += counter_days; } if (includeHours) { counter_html += counter_hours; } if (includeMinutes) { counter_html += counter_minutes; } if (includeSeconds) { counter_html += counter_seconds; } /** * Return the Counter HTML */ return counter_html; } /** * Next date via schedule or attribute */ var nextDayByName = function(scheduledDay) { /** * Date Strings * * Setup some date string to calculate futures events */ var D = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], M = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; /** * Get the key for the scheduleDay */ for (var prop in D) { if (scheduledDay == D[prop]) { var whichNext = prop; } } /** * Current Date * * This is used to figure out the time difference to a * scheduled or data-countdown reference */ var date = new Date(); /** * Time Difference * * Calcuate the difference between the current date and scheduled date. Once * this has been calculated, we'll pass a human readable date back to the * countdown method. */ var dif = date.getDay() - whichNext; dif = dif > 0 ? dif = 7 - dif : -dif; date.setDate(date.getDate() + dif); date.setHours(1); //DST pseudobug correction // get the day var dd = date.getDate(); dd < 0 ? dd = '0' + dd : null; // get the year var yyyy = date.getFullYear(); // get the month var mm = M[date.getMonth()]; /** * Human Readable Date String * * Set the human readable datestring to be passed back, ex. January 01, 2013 */ var nextDatebyDayofWeek = mm + ' ' + dd + ', ' + yyyy; return nextDatebyDayofWeek; } }(jQuery));