Spaces:
Running
Running
/** | |
* SyoTimer - countdown jquery plugin | |
* @version: 2.0.0 | |
* @author: John Syomochkin | |
* @homepage: https://github.com/mrfratello/SyoTimer#readme | |
* @date: 2017.6.24 | |
* @license: under MIT license | |
*/ | |
(function($){ | |
var DAY = "day", | |
HOUR = "hour", | |
MINUTE = "minute", | |
SECOND = "second"; | |
var DAY_IN_SEC = 24 * 60 * 60; | |
var HOUR_IN_SEC = 60 * 60; | |
var MINUTE_IN_SEC = 60; | |
var LAYOUT_TYPES = { | |
d: DAY, | |
h: HOUR, | |
m: MINUTE, | |
s: SECOND | |
}; | |
var UNIT_LINKED_LIST = { | |
list: [SECOND, MINUTE, HOUR, DAY], | |
next: function(current) { | |
var currentIndex = this.list.indexOf(current); | |
return (currentIndex < this.list.length ) ? this.list[currentIndex + 1] : false; | |
}, | |
prev: function(current) { | |
var currentIndex = this.list.indexOf(current); | |
return (currentIndex > 0 ) ? this.list[currentIndex - 1] : false; | |
} | |
}; | |
var DEFAULTS = { | |
year: 2014, | |
month: 7, | |
day: 31, | |
hour: 0, | |
minute: 0, | |
second: 0, | |
timeZone: 'local', // setting the time zone of deadline. | |
// If 'local' then the time zone is ignored and | |
// the deadline is determined by local time of the user. | |
// Otherwise, specifies the offset from the UTC | |
ignoreTransferTime: false, // If `true` then transfer to summer/winter time will not be considered. | |
layout: 'dhms', // sets an order of layout of units of the timer: | |
// days (d) of hours ('h'), minute ('m'), second ('s'). | |
periodic: false, //`true` - the timer is periodic. | |
// If the date until which counts the timer is reached, | |
// the next value date which will count down | |
// the timer is incremented by the value `periodInterval` | |
periodInterval: 7, // the period of the timer in `periodUnit` | |
// (if `periodic` is set to `true`) | |
periodUnit: 'd', // the unit of measurement period timer | |
doubleNumbers: true, // `true` - show hours, minutes and seconds with leading zeros | |
// (2 hours 5 minutes 4 seconds = 02:05:04) | |
effectType: 'none', // The effect of changing the value of seconds | |
lang: 'eng', // localization of a countdown signatures (days, hours, minutes, seconds) | |
headTitle: '', // text above the countdown (may be as html string) | |
footTitle: '', // text under the countdown (may be as html string) | |
afterDeadline: function(timerBlock){ | |
timerBlock.bodyBlock.html('<p style="font-size: 1.2em;">The countdown is finished!</p>'); | |
} | |
}; | |
var ITEMS_HAS_OPTIONS = { | |
second: false, | |
minute: false, | |
hour: false, | |
day: false | |
}; | |
var SyoTimer = { | |
/** | |
* Init syotimer on DOM | |
* @param settings | |
* @returns {Array|Object|*} | |
*/ | |
init: function(settings) { | |
var options = $.extend({}, DEFAULTS, settings || {}); | |
options.itemTypes = staticMethod.getItemTypesByLayout(options.layout); | |
options._itemsHas = $.extend({}, ITEMS_HAS_OPTIONS); | |
for (var i = 0; i < options.itemTypes.length; i++) { | |
options._itemsHas[options.itemTypes[i]] = true; | |
} | |
return this.each(function() { | |
var elementBox = $(this); | |
elementBox.data('syotimer-options', options); | |
SyoTimer._render.apply(this, []); | |
SyoTimer._perSecondHandler.apply(this, []); | |
}); | |
}, | |
/** | |
* Rendering base elements of countdown | |
* @private | |
*/ | |
_render: function() { | |
var elementBox = $(this), | |
options = elementBox.data('syotimer-options'); | |
var timerItem = staticMethod.getTimerItem(), | |
headBlock = $('<div/>', {"class": 'syotimer__head'}) | |
.html(options.headTitle), | |
bodyBlock = $('<div/>', {"class": 'syotimer__body'}), | |
footBlock = $('<div/>', {"class": 'syotimer__footer'}) | |
.html(options.footTitle), | |
itemBlocks = {}; | |
for (var i = 0; i < options.itemTypes.length; i++) { | |
var item = timerItem.clone(); | |
item.addClass('syotimer-cell_type_' + options.itemTypes[i]); | |
bodyBlock.append(item); | |
itemBlocks[options.itemTypes[i]] = item; | |
} | |
var timerBlocks = { | |
headBlock: headBlock, | |
bodyBlock: bodyBlock, | |
footBlock: footBlock | |
}; | |
elementBox.data('syotimer-blocks', timerBlocks) | |
.data('syotimer-items', itemBlocks) | |
.addClass('syotimer') | |
.append(headBlock) | |
.append(bodyBlock) | |
.append(footBlock); | |
}, | |
/** | |
* Handler called per seconds while countdown is not over | |
* @private | |
*/ | |
_perSecondHandler: function() { | |
var elementBox = $(this), | |
options = elementBox.data('syotimer-options'); | |
$('.syotimer-cell > .syotimer-cell__value', elementBox).css( 'opacity', 1 ); | |
var currentDate = new Date(), | |
deadLineDate = new Date( | |
options.year, | |
options.month - 1, | |
options.day, | |
options.hour, | |
options.minute, | |
options.second | |
), | |
differenceInMilliSec = staticMethod.getDifferenceWithTimezone(currentDate, deadLineDate, options), | |
secondsToDeadLine = staticMethod.getSecondsToDeadLine(differenceInMilliSec, options); | |
if ( secondsToDeadLine >= 0 ) { | |
SyoTimer._refreshUnitsDom.apply(this, [secondsToDeadLine]); | |
SyoTimer._applyEffectSwitch.apply(this, [options.effectType]); | |
} else { | |
elementBox = $.extend(elementBox, elementBox.data('syotimer-blocks')); | |
options.afterDeadline( elementBox ); | |
} | |
}, | |
/** | |
* Refresh unit DOM of countdown | |
* @param secondsToDeadLine | |
* @private | |
*/ | |
_refreshUnitsDom: function(secondsToDeadLine) { | |
var elementBox = $(this), | |
options = elementBox.data('syotimer-options'), | |
itemBlocks = elementBox.data('syotimer-items'), | |
unitList = options.itemTypes, | |
unitsToDeadLine = staticMethod.getUnitsToDeadLine( secondsToDeadLine ); | |
if ( !options._itemsHas.day ) { | |
unitsToDeadLine.hour += unitsToDeadLine.day * 24; | |
} | |
if ( !options._itemsHas.hour ) { | |
unitsToDeadLine.minute += unitsToDeadLine.hour * 60; | |
} | |
if ( !options._itemsHas.minute ) { | |
unitsToDeadLine.second += unitsToDeadLine.minute * 60; | |
} | |
for(var i = 0; i < unitList.length; i++) { | |
var unit = unitList[i], | |
unitValue = unitsToDeadLine[unit], | |
itemBlock = itemBlocks[unit]; | |
itemBlock.data('syotimer-unit-value', unitValue); | |
$('.syotimer-cell__value', itemBlock).html(staticMethod.format2( | |
unitValue, | |
(unit !== DAY) ? options.doubleNumbers : false | |
)); | |
$('.syotimer-cell__unit', itemBlock).html($.syotimerLang.getNumeral( | |
unitValue, | |
options.lang, | |
unit | |
)); | |
} | |
}, | |
/** | |
* Applying effect of changing numbers | |
* @param effectType | |
* @param unit | |
* @private | |
*/ | |
_applyEffectSwitch: function(effectType, unit) { | |
unit = unit || SECOND; | |
var element = this, | |
elementBox = $(element); | |
if ( effectType === 'none' ) { | |
setTimeout(function () { | |
SyoTimer._perSecondHandler.apply(element, []); | |
}, 1000); | |
} else if ( effectType === 'opacity' ) { | |
var itemBlocks = elementBox.data('syotimer-items'), | |
unitItemBlock = itemBlocks[unit]; | |
if (unitItemBlock) { | |
var nextUnit = UNIT_LINKED_LIST.next(unit), | |
unitValue = unitItemBlock.data('syotimer-unit-value'); | |
$('.syotimer-cell__value', unitItemBlock).animate( | |
{opacity: 0.1}, | |
1000, | |
'linear', | |
function () { | |
SyoTimer._perSecondHandler.apply(element, []); | |
} | |
); | |
if (nextUnit && unitValue === 0) { | |
SyoTimer._applyEffectSwitch.apply(element, [effectType, nextUnit]); | |
} | |
} | |
} | |
} | |
}; | |
var staticMethod = { | |
/** | |
* Return once cell DOM of countdown: day, hour, minute, second | |
* @returns {object} | |
*/ | |
getTimerItem: function() { | |
var timerCellValue = $('<div/>', { | |
"class": 'syotimer-cell__value', | |
"text": '0' | |
}), | |
timerCellUnit = $('<div/>', {"class": 'syotimer-cell__unit'}), | |
timerCell = $('<div/>', {"class": 'syotimer-cell'}); | |
timerCell.append(timerCellValue) | |
.append(timerCellUnit); | |
return timerCell; | |
}, | |
getItemTypesByLayout: function(layout) { | |
var itemTypes = []; | |
for (var i = 0; i < layout.length; i++) { | |
itemTypes.push(LAYOUT_TYPES[layout[i]]); | |
} | |
return itemTypes; | |
}, | |
/** | |
* Getting count of seconds to deadline | |
* @param differenceInMilliSec | |
* @param options | |
* @returns {*} | |
*/ | |
getSecondsToDeadLine: function(differenceInMilliSec, options) { | |
var secondsToDeadLine, | |
differenceInSeconds = differenceInMilliSec / 1000; | |
differenceInSeconds = Math.floor( differenceInSeconds ); | |
if ( options.periodic ) { | |
var additionalInUnit, | |
differenceInUnit, | |
periodUnitInSeconds = staticMethod.getPeriodUnit(options.periodUnit), | |
fullTimeUnitsBetween = differenceInMilliSec / (periodUnitInSeconds * 1000); | |
fullTimeUnitsBetween = Math.ceil( fullTimeUnitsBetween ); | |
fullTimeUnitsBetween = Math.abs( fullTimeUnitsBetween ); | |
if ( differenceInSeconds >= 0 ) { | |
differenceInUnit = fullTimeUnitsBetween % options.periodInterval; | |
differenceInUnit = ( differenceInUnit === 0 )? options.periodInterval : differenceInUnit; | |
differenceInUnit -= 1; | |
} else { | |
differenceInUnit = options.periodInterval - fullTimeUnitsBetween % options.periodInterval; | |
} | |
additionalInUnit = differenceInSeconds % periodUnitInSeconds; | |
// fix когда дедлайн раньше текущей даты, | |
// возникает баг с неправильным расчетом интервала при different пропорциональной periodUnit | |
if ( ( additionalInUnit === 0 ) && ( differenceInSeconds < 0 ) ) { | |
differenceInUnit--; | |
} | |
secondsToDeadLine = Math.abs( differenceInUnit * periodUnitInSeconds + additionalInUnit ); | |
} else { | |
secondsToDeadLine = differenceInSeconds; | |
} | |
return secondsToDeadLine; | |
}, | |
/** | |
* Getting count of units to deadline | |
* @param secondsToDeadLine | |
* @returns {{}} | |
*/ | |
getUnitsToDeadLine: function(secondsToDeadLine) { | |
var unit = DAY, | |
unitsToDeadLine = {}; | |
do { | |
var unitInMilliSec = staticMethod.getPeriodUnit(unit); | |
unitsToDeadLine[unit] = Math.floor(secondsToDeadLine / unitInMilliSec); | |
secondsToDeadLine = secondsToDeadLine % unitInMilliSec; | |
} while (unit = UNIT_LINKED_LIST.prev(unit)); | |
return unitsToDeadLine; | |
}, | |
/** | |
* Determine a unit of period in milliseconds | |
* @param given_period_unit | |
* @returns {number} | |
*/ | |
getPeriodUnit: function(given_period_unit) { | |
switch (given_period_unit) { | |
case 'd': | |
case DAY: | |
return DAY_IN_SEC; | |
case 'h': | |
case HOUR: | |
return HOUR_IN_SEC; | |
case 'm': | |
case MINUTE: | |
return MINUTE_IN_SEC; | |
case 's': | |
case SECOND: | |
return 1; | |
} | |
}, | |
getDifferenceWithTimezone: function(currentDate, deadLineDate, options) { | |
var differenceByLocalTimezone = deadLineDate.getTime() - currentDate.getTime(), | |
amendmentOnTimezone = 0, | |
amendmentOnTransferTime = 0, | |
amendment; | |
if ( options.timeZone !== 'local' ) { | |
var timezoneOffset = parseFloat(options.timeZone) * staticMethod.getPeriodUnit(HOUR), | |
localTimezoneOffset = - currentDate.getTimezoneOffset() * staticMethod.getPeriodUnit(MINUTE); | |
amendmentOnTimezone = (timezoneOffset - localTimezoneOffset) * 1000; | |
} | |
if ( options.ignoreTransferTime ) { | |
var currentTimezoneOffset = -currentDate.getTimezoneOffset() * staticMethod.getPeriodUnit(MINUTE), | |
deadLineTimezoneOffset = -deadLineDate.getTimezoneOffset() * staticMethod.getPeriodUnit(MINUTE); | |
amendmentOnTransferTime = (currentTimezoneOffset - deadLineTimezoneOffset) * 1000; | |
} | |
amendment = amendmentOnTimezone + amendmentOnTransferTime; | |
return differenceByLocalTimezone - amendment; | |
}, | |
/** | |
* Formation of numbers with leading zeros | |
* @param number | |
* @param isUse | |
* @returns {string} | |
*/ | |
format2: function(number, isUse) { | |
isUse = (isUse !== false); | |
return ( ( number <= 9 ) && isUse ) ? ( "0" + number ) : ( "" + number ); | |
} | |
}; | |
var methods = { | |
setOption: function(name, value) { | |
var elementBox = $(this), | |
options = elementBox.data('syotimer-options'); | |
if ( options.hasOwnProperty( name ) ) { | |
options[name] = value; | |
elementBox.data('syotimer-options', options); | |
} | |
} | |
}; | |
$.fn.syotimer = function(options){ | |
if ( typeof options === 'string' && ( options === "setOption" ) ) { | |
var otherArgs = Array.prototype.slice.call(arguments, 1); | |
return this.each(function() { | |
methods[options].apply( this, otherArgs ); | |
}); | |
} else if (options === null || typeof options === 'object'){ | |
return SyoTimer.init.apply(this, [options]); | |
} else { | |
$.error('SyoTimer. Error in call methods: methods is not exist'); | |
} | |
}; | |
$.syotimerLang = { | |
rus: { | |
second: ['секунда', 'секунды', 'секунд'], | |
minute: ['минута', 'минуты', 'минут'], | |
hour: ['час', 'часа', 'часов'], | |
day: ['день', 'дня', 'дней'], | |
handler: 'rusNumeral' | |
}, | |
eng: { | |
second: ['second', 'seconds'], | |
minute: ['minute', 'minutes'], | |
hour: ['hour', 'hours'], | |
day: ['day', 'days'] | |
}, | |
por: { | |
second: ['segundo', 'segundos'], | |
minute: ['minuto', 'minutos'], | |
hour: ['hora', 'horas'], | |
day: ['dia', 'dias'] | |
}, | |
spa: { | |
second: ['segundo', 'segundos'], | |
minute: ['minuto', 'minutos'], | |
hour: ['hora', 'horas'], | |
day: ['día', 'días'] | |
}, | |
heb: { | |
second: ['שניה', 'שניות'], | |
minute: ['דקה', 'דקות'], | |
hour: ['שעה', 'שעות'], | |
day: ['יום', 'ימים'] | |
}, | |
/** | |
* Universal function for get correct inducement of nouns after a numeral (`number`) | |
* @param number | |
* @returns {number} | |
*/ | |
universal: function(number) { | |
return ( number === 1 ) ? 0 : 1; | |
}, | |
/** | |
* Get correct inducement of nouns after a numeral for Russian language (rus) | |
* @param number | |
* @returns {number} | |
*/ | |
rusNumeral: function(number) { | |
var cases = [2, 0, 1, 1, 1, 2], | |
index; | |
if ( number % 100 > 4 && number % 100 < 20 ) { | |
index = 2; | |
} else { | |
index = cases[(number % 10 < 5) ? number % 10 : 5]; | |
} | |
return index; | |
}, | |
/** | |
* Getting the correct declension of words after numerals | |
* @param number | |
* @param lang | |
* @param unit | |
* @returns {string} | |
*/ | |
getNumeral: function(number, lang, unit) { | |
var handlerName = $.syotimerLang[lang].handler || 'universal', | |
index = this[handlerName](number); | |
return $.syotimerLang[lang][unit][index]; | |
} | |
}; | |
})(jQuery); | |