// AMD support (Thanks to @FagnerMartinsBrack)
;(function(factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
define(['jquery'], factory);
} else {
factory(jQuery);
}
})(function($){
'use strict';
var instances = [],
matchers = [],
defaultOptions = {
precision: 100, // 0.1 seconds, used to update the DOM
elapse: false,
defer: false
};
// Miliseconds
matchers.push(/^[0-9]*$/.source);
// Month/Day/Year [hours:minutes:seconds]
matchers.push(/([0-9]{1,2}\/){2}[0-9]{4}( [0-9]{1,2}(:[0-9]{2}){2})?/
.source);
// Year/Day/Month [hours:minutes:seconds] and
// Year-Day-Month [hours:minutes:seconds]
matchers.push(/[0-9]{4}([\/\-][0-9]{1,2}){2}( [0-9]{1,2}(:[0-9]{2}){2})?/
.source);
// Cast the matchers to a regular expression object
matchers = new RegExp(matchers.join('|'));
// Parse a Date formatted has String to a native object
function parseDateString(dateString) {
// Pass through when a native object is sent
if(dateString instanceof Date) {
return dateString;
}
// Caste string to date object
if(String(dateString).match(matchers)) {
// If looks like a milisecond value cast to number before
// final casting (Thanks to @msigley)
if(String(dateString).match(/^[0-9]*$/)) {
dateString = Number(dateString);
}
// Replace dashes to slashes
if(String(dateString).match(/\-/)) {
dateString = String(dateString).replace(/\-/g, '/');
}
return new Date(dateString);
} else {
throw new Error('Couldn\'t cast `' + dateString +
'` to a date object.');
}
}
// Map to convert from a directive to offset object property
var DIRECTIVE_KEY_MAP = {
'Y': 'years',
'm': 'months',
'n': 'daysToMonth',
'd': 'daysToWeek',
'w': 'weeks',
'W': 'weeksToMonth',
'H': 'hours',
'M': 'minutes',
'S': 'seconds',
'D': 'totalDays',
'I': 'totalHours',
'N': 'totalMinutes',
'T': 'totalSeconds'
};
// Returns an escaped regexp from the string
function escapedRegExp(str) {
var sanitize = str.toString().replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
return new RegExp(sanitize);
}
// Time string formatter
function strftime(offsetObject) {
return function(format) {
var directives = format.match(/%(-|!)?[A-Z]{1}(:[^;]+;)?/gi);
if(directives) {
for(var i = 0, len = directives.length; i < len; ++i) {
var directive = directives[i]
.match(/%(-|!)?([a-zA-Z]{1})(:[^;]+;)?/),
regexp = escapedRegExp(directive[0]),
modifier = directive[1] || '',
plural = directive[3] || '',
value = null;
// Get the key
directive = directive[2];
// Swap shot-versions directives
if(DIRECTIVE_KEY_MAP.hasOwnProperty(directive)) {
value = DIRECTIVE_KEY_MAP[directive];
value = Number(offsetObject[value]);
}
if(value !== null) {
// Pluralize
if(modifier === '!') {
value = pluralize(plural, value);
}
// Add zero-padding
if(modifier === '') {
if(value < 10) {
value = '0' + value.toString();
}
}
// Replace the directive
format = format.replace(regexp, value.toString());
}
}
}
format = format.replace(/%%/, '%');
return format;
};
}
// Pluralize
function pluralize(format, count) {
var plural = 's', singular = '';
if(format) {
format = format.replace(/(:|;|\s)/gi, '').split(/\,/);
if(format.length === 1) {
plural = format[0];
} else {
singular = format[0];
plural = format[1];
}
}
// Fix #187
if(Math.abs(count) > 1) {
return plural;
} else {
return singular;
}
}
// The Final Countdown
var Countdown = function(el, finalDate, options) {
this.el = el;
this.$el = $(el);
this.interval = null;
this.offset = {};
this.options = $.extend({}, defaultOptions);
// console.log(this.options);
// This helper variable is necessary to mimick the previous check for an
// event listener on this.$el. Because of the event loop there might not
// be a registered event listener during the first tick. In order to work
// as expected a second tick is necessary, so that the events can be fired
// and handled properly.
this.firstTick = true;
// Register this instance
this.instanceNumber = instances.length;
instances.push(this);
// Save the reference
this.$el.data('countdown-instance', this.instanceNumber);
// Handle options or callback
if (options) {
// Register the callbacks when supplied
if(typeof options === 'function') {
this.$el.on('update.countdown', options);
this.$el.on('stoped.countdown', options);
this.$el.on('finish.countdown', options);
} else {
this.options = $.extend({}, defaultOptions, options);
}
}
// Set the final date and start
this.setFinalDate(finalDate);
// Starts the countdown automatically unless it's defered,
// Issue #198
if (this.options.defer === false) {
this.start();
}
};
$.extend(Countdown.prototype, {
start: function() {
if(this.interval !== null) {
clearInterval(this.interval);
}
var self = this;
this.update();
this.interval = setInterval(function() {
self.update.call(self);
}, this.options.precision);
},
stop: function() {
clearInterval(this.interval);
this.interval = null;
this.dispatchEvent('stoped');
},
toggle: function() {
if (this.interval) {
this.stop();
} else {
this.start();
}
},
pause: function() {
this.stop();
},
resume: function() {
this.start();
},
remove: function() {
this.stop.call(this);
instances[this.instanceNumber] = null;
// Reset the countdown instance under data attr (Thanks to @assiotis)
delete this.$el.data().countdownInstance;
},
setFinalDate: function(value) {
this.finalDate = parseDateString(value); // Cast the given date
},
update: function() {
// Stop if dom is not in the html (Thanks to @dleavitt)
if(this.$el.closest('html').length === 0) {
this.remove();
return;
}
var now = new Date(),
newTotalSecsLeft;
// Create an offset date object
newTotalSecsLeft = this.finalDate.getTime() - now.getTime(); // Millisecs
// Calculate the remaining time
newTotalSecsLeft = Math.ceil(newTotalSecsLeft / 1000); // Secs
// If is not have to elapse set the finish
newTotalSecsLeft = !this.options.elapse && newTotalSecsLeft < 0 ? 0 :
Math.abs(newTotalSecsLeft);
// Do not proceed to calculation if the seconds have not changed or
// during the first tick
if (this.totalSecsLeft === newTotalSecsLeft || this.firstTick) {
this.firstTick = false;
return;
} else {
this.totalSecsLeft = newTotalSecsLeft;
}
// Check if the countdown has elapsed
this.elapsed = (now >= this.finalDate);
// Calculate the offsets
this.offset = {
seconds : this.totalSecsLeft % 60,
minutes : Math.floor(this.totalSecsLeft / 60) % 60,
hours : Math.floor(this.totalSecsLeft / 60 / 60) % 24,
days : Math.floor(this.totalSecsLeft / 60 / 60 / 24) % 7,
daysToWeek : Math.floor(this.totalSecsLeft / 60 / 60 / 24) % 7,
daysToMonth : Math.floor(this.totalSecsLeft / 60 / 60 / 24 % 30.4368),
weeks : Math.floor(this.totalSecsLeft / 60 / 60 / 24 / 7),
weeksToMonth: Math.floor(this.totalSecsLeft / 60 / 60 / 24 / 7) % 4,
months : Math.floor(this.totalSecsLeft / 60 / 60 / 24 / 30.4368),
years : Math.abs(this.finalDate.getFullYear()-now.getFullYear()),
totalDays : Math.floor(this.totalSecsLeft / 60 / 60 / 24),
totalHours : Math.floor(this.totalSecsLeft / 60 / 60),
totalMinutes: Math.floor(this.totalSecsLeft / 60),
totalSeconds: this.totalSecsLeft
};
// Dispatch an event
if(!this.options.elapse && this.totalSecsLeft === 0) {
this.stop();
this.dispatchEvent('finish');
} else {
this.dispatchEvent('update');
}
},
dispatchEvent: function(eventName) {
var event = $.Event(eventName + '.countdown');
event.finalDate = this.finalDate;
event.elapsed = this.elapsed;
event.offset = $.extend({}, this.offset);
event.strftime = strftime(this.offset);
this.$el.trigger(event);
}
});
// Register the jQuery selector actions
$.fn.countdown = function() {
var argumentsArray = Array.prototype.slice.call(arguments, 0);
return this.each(function() {
// If no data was set, jQuery.data returns undefined
var instanceNumber = $(this).data('countdown-instance');
// Verify if we already have a countdown for this node ...
// Fix issue #22 (Thanks to @romanbsd)
if (instanceNumber !== undefined) {
var instance = instances[instanceNumber],
method = argumentsArray[0];
// If method exists in the prototype execute
if(Countdown.prototype.hasOwnProperty(method)) {
instance[method].apply(instance, argumentsArray.slice(1));
// If method look like a date try to set a new final date
} else if(String(method).match(/^[$A-Z_][0-9A-Z_$]*$/i) === null) {
instance.setFinalDate.call(instance, method);
// Allow plugin to restart after finished
// Fix issue #38 (thanks to @yaoazhen)
instance.start();
} else {
$.error('Method %s does not exist on jQuery.countdown'
.replace(/\%s/gi, method));
}
} else {
// ... if not we create an instance
new Countdown(this, argumentsArray[0], argumentsArray[1]);
}
});
};
});
Good code but need to change background to increase visibility of code
ReplyDeleteThanks.
Thank you and surely we will keep this thing in mind
ReplyDelete