/*global jQuery:false */ /* * ======================= * jQuery Timer Plugin * ======================= * Start/Stop/Resume a time in any HTML element */ (function($) { // PRIVATE var intr, totalSeconds = 0, isTimerRunning = false, startTime, duration = null, options = { seconds: 0, // default seconds value to start timer from editable: false, // this will let users make changes to the time restart: false, // this will enable stop or continue after a timer callback duration: null, // duration to run callback after // callback to run after elapsed duration callback: function() { alert('Time up!'); stopTimerInterval(); }, repeat: false, // this will repeat callback every n times duration is elapsed countdown: false, // if true, this will render the timer as a countdown if duration > 0 format: null // this sets the format in which the time will be printed }, $el, display = 'html'; // to be used as $el.html in case of div and $el.val in case of input type text /** * Common function to start or resume a timer interval */ function startTimerInterval() { intr = setInterval(incrementSeconds, 500); isTimerRunning = true; } /** * Common function to stop timer interval * @return {[type]} [description] */ function stopTimerInterval() { clearInterval(intr); isTimerRunning = false; } /** * Increment total seconds by subtracting startTime from the current unix timestamp in seconds * and call render to display pretty time */ function incrementSeconds() { totalSeconds = getUnixSeconds() - startTime; render(); // Check if totalSeconds is equal to duration if any if (duration && totalSeconds === duration) { // Run the default callback options.callback(); // If repeat is requested, bump the duration by options.duration if (options.repeat) { duration += options.duration; } // If this is a countdown, then end it as duration has completed if (options.countdown) { options.countdown = false; } } } /** * Render pretty time */ function render() { var sec = totalSeconds; if (options.countdown && duration > 0) { sec = duration - totalSeconds; } $el[display](secondsToTime(sec)); $el.data('seconds', sec); } function makeEditable() { $el.on('focus', function() { pauseTimer(); }); $el.on('blur', function() { // eg. 12 sec 3:34 min 12:30 min var val = $el[display](), valArr; if (val.indexOf('sec') > 0) { // sec totalSeconds = Number(val.replace(/\ssec/g, '')); } else if (val.indexOf('min') > 0) { // min val = val.replace(/\smin/g, ''); valArr = val.split(':'); totalSeconds = Number(valArr[0] * 60) + Number(valArr[1]); } else if (val.match(/\d{1,2}:\d{2}:\d{2}/)) { // hrs valArr = val.split(':'); totalSeconds = Number(valArr[0] * 3600) + Number(valArr[1] * 60) + Number(valArr[2]); } resumeTimer(); }); } /** * Get the current unix timestamp in seconds * @return {Number} [unix timestamp in seconds] */ function getUnixSeconds() { return Math.round(new Date().getTime() / 1000); } /** * Convert a number of seconds into an object of hours, minutes and seconds * @param {Number} sec [Number of seconds] * @return {Object} [An object with hours, minutes and seconds representation of the given seconds] */ function sec2TimeObj(sec) { var hours = 0, minutes = Math.floor(sec / 60), seconds; // Hours if (sec >= 3600) { hours = Math.floor(sec / 3600); } // Minutes if (sec >= 3600) { minutes = Math.floor(sec % 3600 / 60); } // Prepend 0 to minutes under 10 if (minutes < 10 && hours > 0) { minutes = '0' + minutes; } // Seconds seconds = sec % 60; // Prepend 0 to seconds under 10 if (seconds < 10 && (minutes > 0 || hours > 0)) { seconds = '0' + seconds; } return { hours: hours, minutes: minutes, seconds: seconds }; } /** * Convert the given seconds to an object made up of hours, minutes and seconds and return a pretty display * @param {Number} sec [Second to display as pretty time] * @return {String} [Pretty time] */ function secondsToTime(sec) { var time = '', timeObj = sec2TimeObj(sec); if (options.format) { var formatDef = [ {identifier: '%h', value: timeObj.hours, pad: false}, {identifier: '%m', value: timeObj.minutes, pad: false}, {identifier: '%s', value: timeObj.seconds, pad: false}, {identifier: '%H', value: parseInt(timeObj.hours), pad: true}, {identifier: '%M', value: parseInt(timeObj.minutes), pad: true}, {identifier: '%S', value: parseInt(timeObj.seconds), pad: true} ]; time = options.format; formatDef.forEach(function(format) { time = time.replace( new RegExp(format.identifier.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1'), 'g'), (format.pad) ? ((format.value < 10) ? '0' + format.value : format.value) : format.value ); }); } else { if (timeObj.hours) { time = timeObj.hours + ':' + timeObj.minutes + ':' + timeObj.seconds; } else { if (timeObj.minutes) { time = timeObj.minutes + ':' + timeObj.seconds + ' min'; } else { time = timeObj.seconds + ' sec'; } } } return time; } /** * Convert a string time like 5m30s to seconds * If a number (eg 300) is provided, then return as is * @param {Number|String} time [The human time to convert to seconds] * @return {Number} [Number of seconds] */ function timeToSeconds(time) { // In case the passed arg is a number, then use that as number of seconds if (!isNaN(Number(time))) { return time; } var hMatch = time.match(/\d{1,2}h/), mMatch = time.match(/\d{1,2}m/), sMatch = time.match(/\d{1,2}s/), seconds = 0; time = time.toLowerCase(); // @todo: throw an error in case of faulty time value like 5m61s or 61m if (hMatch) { seconds += Number(hMatch[0].replace('h', '')) * 3600; } if (mMatch) { seconds += Number(mMatch[0].replace('m', '')) * 60; } if (sMatch) { seconds += Number(sMatch[0].replace('s', '')); } return seconds; } // TIMER INTERFACE function startTimer() { if (!isTimerRunning) { render(); startTimerInterval(); } } function pauseTimer() { if (isTimerRunning) { stopTimerInterval(); } } function resumeTimer() { if (!isTimerRunning) { startTime = getUnixSeconds() - totalSeconds; startTimerInterval(); } } function resetTimer() { startTime = getUnixSeconds(); totalSeconds = 0; $el.data('seconds', totalSeconds); duration = options.duration; } function removeTimer() { stopTimerInterval(); $el.data('plugin_' + pluginName, null); $el.data('seconds', null); $el[display](''); } // TIMER PROTOTYPE var Timer = function(element, userOptions) { var elementType; options = $.extend(options, userOptions); $el = $(element); // Setup total seconds from options.seconds (if any) totalSeconds = options.seconds; // Setup start time if seconds were provided startTime = getUnixSeconds() - totalSeconds; $el.data('seconds', totalSeconds); // Check if this is a input/textarea element or not elementType = $el.prop('tagName').toLowerCase(); if (elementType === 'input' || elementType === 'textarea') { display = 'val'; } if (options.duration) { duration = options.duration = timeToSeconds(options.duration); } if (options.editable) { makeEditable(); } }; /** * Initialize the plugin with public methods */ Timer.prototype = { start: function() { startTimer(); }, pause: function() { pauseTimer(); }, resume: function() { resumeTimer(); }, reset: function() { resetTimer(); }, remove: function() { removeTimer(); } }; // INITIALIZE THE PLUGIN var pluginName = 'timer'; $.fn[pluginName] = function(options) { options = options || 'start'; return this.each(function() { /* Allow the plugin to be initialized on an element only once This way we can call the plugin's internal function without having to reinitialize the plugin all over again. */ if (!($.data(this, 'plugin_' + pluginName) instanceof Timer)) { /* Create a new data attribute on the element to hold the plugin name This way we can know which plugin(s) is/are initialized on the element later */ $.data(this, 'plugin_' + pluginName, new Timer(this, options)); } /* Use the instance of this plugin derived from the data attribute for this element to conduct whatever action requested as a string parameter. */ var instance = $.data(this, 'plugin_' + pluginName); /* Provision for calling a function from this plugin without initializing it all over again */ if (typeof options === 'string') { if (typeof instance[options] === 'function') { /* Pass in 'instance' to provide for the value of 'this' in the called function */ instance[options].call(instance); } } /** * Allow passing custom options object */ if (typeof options === 'object') { instance.start.call(instance); } }); }; })(jQuery);