PageSourceSearch

https://www.jaroslavsvarc.cz/wp-content/plugins/countdown-builder/assets/js/TimeCircles.js?ver=2.9

js jaroslavsvarc.cz collected 2026-10-03 00:09:24 UTC 35,294 bytes, 997 lines download raw bytes

vendor: 2,387 bytes, lines 1-63
1/**
2 * Basic structure: TC_Class is the public class that is returned upon being called
3 * 
4 * So, if you do
5 *      var tc = $(".timer").TimeCircles();
6 *      
7 * tc will contain an instance of the public TimeCircles class. It is important to
8 * note that TimeCircles is not chained in the conventional way, check the
9 * documentation for more info on how TimeCircles can be chained.
10 * 
11 * After being called/created, the public TimerCircles class will then- for each element
12 * within it's collection, either fetch or create an instance of the private class.
13 * Each function called upon the public class will be forwarded to each instance
14 * of the private classes within the relevant element collection
15 **/
16(function($) {
17
18    var useWindow = window;
19    
20    // From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
21    if (!Object.keys) {
22        Object.keys = (function() {
23            'use strict';
24            var hasOwnProperty = Object.prototype.hasOwnProperty,
25                    hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'),
26                    dontEnums = [
27                        'toString',
28                        'toLocaleString',
29                        'valueOf',
30                        'hasOwnProperty',
31                        'isPrototypeOf',
32                        'propertyIsEnumerable',
33                        'constructor'
34                    ],
35                    dontEnumsLength = dontEnums.length;
36
37            return function(obj) {
38                if (typeof obj !== 'object' && (typeof obj !== 'function' || obj === null)) {
39                    throw new TypeError('Object.keys called on non-object');
40                }
41
42                var result = [], prop, i;
43
44                for (prop in obj) {
45                    if (hasOwnProperty.call(obj, prop)) {
46                        result.push(prop);
47                    }
48                }
49
50                if (hasDontEnumBug) {
51                    for (i = 0; i < dontEnumsLength; i++) {
52                        if (hasOwnProperty.call(obj, dontEnums[i])) {
53                            result.push(dontEnums[i]);
54                        }
55                    }
56                }
57                return result;
58            };
59        }());
60    }
61    
62    // Used to disable some features on IE8
63    var limited_mode = false;
64    var tick_duration = 200; // in ms
65    
66    var debug = (location.hash === "#debug");
67    function debug_log(msg) {
68        if (debug) {
69            console.log(msg);
70        }
71    }
72
73    var allUnits = ["Years", "Months", "Days", "Hours", "Minutes", "Seconds"];
74    var nextUnits = {
75        Seconds: "Minutes",
76        Minutes: "Hours",
77        Hours: "Days",
78        Days: "Months",
79        Months: "Years",
80        Years: "Years"
81    };
82    var secondsIn = {
83        Seconds: 1,
84        Minutes: 60,
85        Hours: 3600,
86        Days: 86400,
87        Months: 2629800,
88        Years: 31557600
89    };
90
91    /**
92     * Converts hex color code into object containing integer values for the r,g,b use
93     * This function (hexToRgb) originates from:
94     * http://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
95     * @param {string} hex color code
96     */
97    function hexToRgb(hex) {
98
99        // Verify already RGB (e.g. "rgb(0,0,0)") or RGBA (e.g. "rgba(0,0,0,0.5)")
100        var rgba = /^rgba?\(([\d]+),([\d]+),([\d]+)(,([\d\.]+))?\)$/;
101        if(rgba.test(hex)) {
102            var result = rgba.exec(hex);
103            return {
104                r: parseInt(result[1]),
105                g: parseInt(result[2]),
106                b: parseInt(result[3]),
107                a: parseInt(result[5] ? result[5] : 1)
108            };
109        }
110
111        // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
112        var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
113        hex = hex.replace(shorthandRegex, function(m, r, g, b) {
114            return r + r + g + g + b + b;
115        });
116
117        var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
118        return result ? {
119            r: parseInt(result[1], 16),
120            g: parseInt(result[2], 16),
121            b: parseInt(result[3], 16)
122        } : null;
123    }
124    
125    function isCanvasSupported() {
126        var elem = document.createElement('canvas');
127        return !!(elem.getContext && elem.getContext('2d'));
128    }
129
130    /**
131     * Function s4() and guid() originate from:
132     * http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
133     */
134    function s4() {
135        return Math.floor((1 + Math.random()) * 0x10000)
136                .toString(16)
137                .substring(1);
138    }
139
140    /**
141     * Creates a unique id
142     * @returns {String}
143     */
144    function guid() {
145        return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
146                s4() + '-' + s4() + s4() + s4();
147    }
148
149    /**
150     * Array.prototype.indexOf fallback for IE8
151     * @param {Mixed} mixed
152     * @returns {Number}
153     */
154    if (!Array.prototype.indexOf) {
155        Array.prototype.indexOf = function(elt /*, from*/)
156        {
157            var len = this.length >>> 0;
158
159            var from = Number(arguments[1]) || 0;
160            from = (from < 0)
161                    ? Math.ceil(from)
162                    : Math.floor(from);
163            if (from < 0)
164                from += len;
165
166            for (; from < len; from++)
167            {
168                if (from in this &&
169                        this[from] === elt)
170                    return from;
171            }
172            return -1;
173        };
174    }
175
176    function parse_date(str) {
177        var match = str.match(/^[0-9]{4}-[0-9]{2}-[0-9]{2}\s[0-9]{1,2}:[0-9]{2}:[0-9]{2}$/);
178        if (match !== null && match.length > 0) {
179            var parts = str.split(" ");
180            var date = parts[0].split("-");
181            var time = parts[1].split(":");
182            return new Date(date[0], date[1] - 1, date[2], time[0], time[1], time[2]);
183        }
184        // Fallback for different date formats
185        var d = Date.parse(str);
186        if (!isNaN(d))
187            return d;
188        d = Date.parse(str.replace(/-/g, '/').replace('T', ' '));
189        if (!isNaN(d))
190            return d;
191        // Cant find anything
192        return new Date();
193    }
194
195    function parse_times(diff, old_diff, total_duration, units, floor) {
196        var raw_time = {};
197        var raw_old_time = {};
198        var time = {};
199        var pct = {};
200        var old_pct = {};
201        var old_time = {};
202
203        var greater_unit = null;
204        var maxUnitName = units[0];
205
206        for(var i = 0; i < units.length; i++) {
207            var unit = units[i];
208            var maxUnits;
209
210            if (greater_unit === null) {
211                maxUnits = total_duration / secondsIn[unit];
212            }
213            else {
214                maxUnits = secondsIn[greater_unit] / secondsIn[unit];
215            }
216
217            var curUnits = (diff / secondsIn[unit]);
218            var oldUnits = (old_diff / secondsIn[unit]);
219            
220            if(floor) {
221                if(curUnits > 0) curUnits = Math.floor(curUnits);
222                else curUnits = Math.ceil(curUnits);
223                if(oldUnits > 0) oldUnits = Math.floor(oldUnits);
224                else oldUnits = Math.ceil(oldUnits);
225            }
226            
227            if (unit !== maxUnitName) {
228                curUnits = curUnits % maxUnits;
229                oldUnits = oldUnits % maxUnits;
230            }
231
232            raw_time[unit] = curUnits;
233            time[unit] = Math.abs(curUnits);
234            raw_old_time[unit] = oldUnits;
235            old_time[unit] = Math.abs(oldUnits);
236            pct[unit] = Math.abs(curUnits) / maxUnits;
237            old_pct[unit] = Math.abs(oldUnits) / maxUnits;
238
239            greater_unit = unit;
240        }
241
242        return {
243            raw_time: raw_time,
244            raw_old_time: raw_old_time,
245            time: time,
246            old_time: old_time,
247            pct: pct,
248            old_pct: old_pct
249        };
250    }
251
252    var TC_Instance_List = {};
253    function updateUsedWindow() {
254        if(typeof useWindow.TC_Instance_List !== "undefined") {
255            TC_Instance_List = useWindow.TC_Instance_List;
256        }
257        else {
258            useWindow.TC_Instance_List = TC_Instance_List;
259        }
260        initializeAnimationFrameHandler(useWindow);
261    };
262    
263    function initializeAnimationFrameHandler(w) {
264        var vendors = ['webkit', 'moz'];
265        for (var x = 0; x < vendors.length && !w.requestAnimationFrame; ++x) {
266            w.requestAnimationFrame = w[vendors[x] + 'RequestAnimationFrame'];
267            w.cancelAnimationFrame = w[vendors[x] + 'CancelAnimationFrame'];
268        }
269
270        if (!w.requestAnimationFrame || !w.cancelAnimationFrame) {
271            w.requestAnimationFrame = function(callback, element, instance) {
272                if (typeof instance === "undefined")
273                    instance = {data: {last_frame: 0}};
274                var currTime = new Date().getTime();
275                var timeToCall = Math.max(0, 16 - (currTime - instance.data.last_frame));
276                var id = w.setTimeout(function() {
277                    callback(currTime + timeToCall);
278                }, timeToCall);
279                instance.data.last_frame = currTime + timeToCall;
280                return id;
281            };
282            w.cancelAnimationFrame = function(id) {
283                clearTimeout(id);
284            };
285        }
286    };
287    
288
289    var TC_Instance = function(element, options) {
290        this.element = element;
291        this.container;
292        this.listeners = null;
293        this.data = {
294            paused: false,
295            last_frame: 0,
296            animation_frame: null,
297            interval_fallback: null,
298            timer: false,
299            total_duration: null,
300            prev_time: null,
301            drawn_units: [],
302            text_elements: {
303                Years: null,
304                Months: null,
305                Days: null,
306                Hours: null,
307                Minutes: null,
308                Seconds: null
309            },
310            attributes: {
311                canvas: null,
312                context: null,
313                item_size: null,
314                line_width: null,
315                radius: null,
316                outer_radius: null
317            },
318            state: {
319                fading: {
320                    Years: false,
321                    Months: false,
322                    Days: false,
323                    Hours: false,
324                    Minutes: false,
325                    Seconds: false
326                }
327            }
328        };
329
330        this.config = null;
331        this.setOptions(options);
332        this.initialize();
333    };
334
335    TC_Instance.prototype.clearListeners = function() {
336        this.listeners = { all: [], visible: [] };
337    };
338    
339    TC_Instance.prototype.addTime = function(seconds_to_add) {
340        if(this.data.attributes.ref_date instanceof Date) {
341            var d = this.data.attributes.ref_date;
342            d.setSeconds(d.getSeconds() + seconds_to_add);
343        }
344        else if(!isNaN(this.data.attributes.ref_date)) {
345            this.data.attributes.ref_date += (seconds_to_add * 1000);
346        }
347    };
348    
349    TC_Instance.prototype.initialize = function(clear_listeners) {
350        // Initialize drawn units
351        this.data.drawn_units = [];
352        for(var i = 0; i < Object.keys(this.config.time).length; i++) {
353            var unit = Object.keys(this.config.time)[i];
354            if (this.config.time[unit].show) {
355                this.data.drawn_units.push(unit);
356            }
357        }
358
359        // Avoid stacking
360        $(this.element).children('div.time_circles').remove();
361
362        if (typeof clear_listeners === "undefined")
363            clear_listeners = true;
364        if (clear_listeners || this.listeners === null) {
365            this.clearListeners();
366        }
367        this.container = $("<div>");
368        this.container.addClass('time_circles');
369        this.container.appendTo(this.element);
370        
371        // Determine the needed width and height of TimeCircles
372        var height = this.element.offsetHeight;
373        var width = this.element.offsetWidth;
374        if (height === 0)
375            height = $(this.element).height();
376        if (width === 0)
377            width = $(this.element).width();
378
379        if (height === 0 && width > 0)
380            height = width / this.data.drawn_units.length;
381        else if (width === 0 && height > 0)
382            width = height * this.data.drawn_units.length;
383        
384        // Create our canvas and set it to the appropriate size
385        var canvasElement = document.createElement('canvas');
386        canvasElement.width = width;
387        canvasElement.height = height;
388        
389        // Add canvas elements
390        this.data.attributes.canvas = $(canvasElement);
391        this.data.attributes.canvas.appendTo(this.container);
392        
393        // Check if the browser has browser support
394        var canvasSupported = isCanvasSupported();
395        // If the browser doesn't have browser support, check if explorer canvas is loaded
396        // (A javascript library that adds canvas support to browsers that don't have it)
397        if(!canvasSupported && typeof G_vmlCanvasManager !== "undefined") {
398            G_vmlCanvasManager.initElement(canvasElement);
399            limited_mode = true;
400            canvasSupported = true;
401        }
402        if(canvasSupported) {
403            this.data.attributes.context = canvasElement.getContext('2d');
404        }
405
406        this.data.attributes.item_size = Math.min(width / this.data.drawn_units.length, height);
407        this.data.attributes.line_width = this.data.attributes.item_size * this.config.fg_width;
408        this.data.attributes.radius = ((this.data.attributes.item_size * 0.8) - this.data.attributes.line_width) / 2;
409        this.data.attributes.outer_radius = this.data.attributes.radius + 0.5 * Math.max(this.data.attributes.line_width, this.data.attributes.line_width * this.config.bg_width);
410
411        // Prepare Time Elements
412        var i = 0;
413        for (var key in this.data.text_elements) {
414            if (!this.config.time[key].show)
415                continue;
416            var textElement = $("<div>");
417            textElement.addClass('textDiv_' + key);
418            textElement.css("top", Math.round(0.35 * this.data.attributes.item_size));
419            textElement.css("left", Math.round(i++ * this.data.attributes.item_size));
420            textElement.css("width", this.data.attributes.item_size);
421            textElement.appendTo(this.container);
422
423            var that = this;
424            var addTextElement = function () {
425	            var headerElement = $("<h4>");
426	            headerElement.text(that.config.time[key].text); // Options
427	            headerElement.css("font-size", Math.round(that.config.text_size * that.data.attributes.item_size));
428	            headerElement.appendTo(textElement);
429            };
430            if (!this.config['ycd-countdown-switch-number']) {
431	            addTextElement();
432            }
433
434            var numberElement = $("<span>");
435            numberElement.css("font-size", Math.round(this.config.number_size * this.data.attributes.item_size));
436            numberElement.appendTo(textElement);
437
438            if (this.config['ycd-countdown-switch-number']) {
439	            addTextElement()
440            }
441
442            this.data.text_elements[key] = numberElement;
443        }
444
445        this.start();
446        if (!this.config.start) {
447            this.data.paused = true;
448        }
449        
450        // Set up interval fallback
451        var _this = this;
452        this.data.interval_fallback = useWindow.setInterval(function(){
453            _this.update.call(_this, true);
454        }, 100);
455    };
456
457    TC_Instance.prototype.update = function(nodraw) {
458        if(typeof nodraw === "undefined") {
459            nodraw = false;
460        }
461        else if(nodraw && this.data.paused) {
462            return;
463        }
464        
465        if(limited_mode) {
466            //Per unit clearing doesn't work in IE8 using explorer canvas, so do it in one time. The downside is that radial fade cant be used
467            this.data.attributes.context.clearRect(0, 0, this.data.attributes.canvas[0].width, this.data.attributes.canvas[0].hright);
468        }
469        var diff, old_diff;
470
471        var prevDate = this.data.prev_time;
472        var curDate = new Date();
473        this.data.prev_time = curDate;
474
475        if (prevDate === null)
476            prevDate = curDate;
477
478        // If not counting past zero, and time < 0, then simply draw the zero point once, and call stop
479        if (!this.config.count_past_zero) {
480            if (curDate > this.data.attributes.ref_date) {
481                for(var i = 0; i < this.data.drawn_units.length; i++) {
482                    var key = this.data.drawn_units[i];
483
484                    // Set the text value
485                    this.data.text_elements[key].text("0");
486                    var x = (i * this.data.attributes.item_size) + (this.data.attributes.item_size / 2);
487                    var y = this.data.attributes.item_size / 2;
488                    var color = this.config.time[key].color;
489                    this.drawArc(x, y, color, 0);
490                }
491                this.stop();
492                return;
493            }
494        }
495
496        // Compare current time with reference
497        diff = (this.data.attributes.ref_date - curDate) / 1000;
498        if(this.config.count_past_zero) {
499            diff = Math.abs(diff);
500        }
501        old_diff = (this.data.attributes.ref_date - prevDate) / 1000;
502
503        var floor = this.config.animation !== "smooth";
504
505        var visible_times = parse_times(diff, old_diff, this.data.total_duration, this.data.drawn_units, floor);
506        var all_times = parse_times(diff, old_diff, secondsIn["Years"], allUnits, floor);
507
508        var i = 0;
509        var j = 0;
510        var lastKey = null;
511
512        var cur_shown = this.data.drawn_units.slice();
513        for (var i in allUnits) {
514            var key = allUnits[i];
515
516            // Notify (all) listeners
517            if (Math.floor(all_times.raw_time[key]) !== Math.floor(all_times.raw_old_time[key])) {
518                this.notifyListeners(key, Math.floor(all_times.time[key]), Math.floor(diff), "all");
519            }
520
521            if (cur_shown.indexOf(key) < 0)
522                continue;
523
524            // Notify (visible) listeners
525            if (Math.floor(visible_times.raw_time[key]) !== Math.floor(visible_times.raw_old_time[key])) {
526                this.notifyListeners(key, Math.floor(visible_times.time[key]), Math.floor(diff), "visible");
527            }
528            
529            if(!nodraw) {
530                // Set the text value
531                this.data.text_elements[key].text(Math.floor(Math.abs(visible_times.time[key])));
532
533                var x = (j * this.data.attributes.item_size) + (this.data.attributes.item_size / 2);
534                var y = this.data.attributes.item_size / 2;
535                var color = this.config.time[key].color;
536
537                if (this.config.animation === "smooth") {
538                    if (lastKey !== null && !limited_mode) {
539                        if (Math.floor(visible_times.time[lastKey]) > Math.floor(visible_times.old_time[lastKey])) {
540                            this.radialFade(x, y, color, 1, key);
541                            this.data.state.fading[key] = true;
542                        }
543                        else if (Math.floor(visible_times.time[lastKey]) < Math.floor(visible_times.old_time[lastKey])) {
544                            this.radialFade(x, y, color, 0, key);
545                            this.data.state.fading[key] = true;
546                        }
547                    }
548                    if (!this.data.state.fading[key]) {
549                        this.drawArc(x, y, color, visible_times.pct[key]);
550                    }
551                }
552                else {
553                    this.animateArc(x, y, color, visible_times.pct[key], visible_times.old_pct[key], (new Date()).getTime() + tick_duration);
554                }
555            }
556            lastKey = key;
557            j++;
558        }
559
560        // Dont request another update if we should be paused
561        if(this.data.paused || nodraw) {
562            return;
563        }
564        
565        // We need this for our next frame either way
566        var _this = this;
567        var update = function() {
568            _this.update.call(_this);
569        };
570
571        // Either call next update immediately, or in a second
572        if (this.config.animation === "smooth") {
573            // Smooth animation, Queue up the next frame
574            this.data.animation_frame = useWindow.requestAnimationFrame(update, _this.element, _this);
575        }
576        else {
577            // Tick animation, Don't queue until very slightly after the next second happens
578            var delay = (diff % 1) * 1000;
579            if (delay < 0)
580                delay = 1000 + delay;
581            delay += 50;
582
583            _this.data.animation_frame = useWindow.setTimeout(function() {
584                _this.data.animation_frame = useWindow.requestAnimationFrame(update, _this.element, _this);
585            }, delay);
586        }
587    };
588
589    TC_Instance.prototype.animateArc = function(x, y, color, target_pct, cur_pct, animation_end) {
590        if (this.data.attributes.context === null)
591            return;
592
593        var diff = cur_pct - target_pct;
594        if (Math.abs(diff) > 0.5) {
595            if (target_pct === 0) {
596                this.radialFade(x, y, color, 1);
597            }
598            else {
599                this.radialFade(x, y, color, 0);
600            }
601        }
602        else {
603            var progress = (tick_duration - (animation_end - (new Date()).getTime())) / tick_duration;
604            if (progress > 1)
605                progress = 1;
606
607            var pct = (cur_pct * (1 - progress)) + (target_pct * progress);
608            this.drawArc(x, y, color, pct);
609
610            //var show_pct =
611            if (progress >= 1)
612                return;
613            var _this = this;
614            useWindow.requestAnimationFrame(function() {
615                _this.animateArc(x, y, color, target_pct, cur_pct, animation_end);
616            }, this.element);
617        }
618    };
619
620    TC_Instance.prototype.drawArc = function(x, y, color, pct) {
621        if (this.data.attributes.context === null)
622            return;
623
624        var clear_radius = Math.max(this.data.attributes.outer_radius, this.data.attributes.item_size / 2);
625        if(!limited_mode) {
626            this.data.attributes.context.clearRect(
627                    x - clear_radius,
628                    y - clear_radius,
629                    clear_radius * 2,
630                    clear_radius * 2
631                    );
632        }
633        
634        if (this.config.use_background) {
635            this.data.attributes.context.beginPath();
636            this.data.attributes.context.arc(x, y, this.data.attributes.radius, 0, 2 * Math.PI, false);
637            this.data.attributes.context.lineWidth = this.data.attributes.line_width * this.config.bg_width;
638
639            // line color
640            this.data.attributes.context.strokeStyle = this.config.circle_bg_color;
641            this.data.attributes.context.stroke();
642        }
643
644        // Direction
645        var startAngle, endAngle, counterClockwise;
646        var defaultOffset = (-0.5 * Math.PI);
647        var fullCircle = 2 * Math.PI;
648        startAngle = defaultOffset + (this.config.start_angle / 360 * fullCircle);
649        var offset = (2 * pct * Math.PI);
650
651        if (this.config.direction === "Both") {
652            counterClockwise = false;
653            startAngle -= (offset / 2);
654            endAngle = startAngle + offset;
655        }
656        else {
657            if (this.config.direction === "Clockwise") {
658                counterClockwise = false;
659                endAngle = startAngle + offset;
660            }
661            else {
662                counterClockwise = true;
663                endAngle = startAngle - offset;
664            }
665        }
666
667        this.data.attributes.context.beginPath();
668        this.data.attributes.context.arc(x, y, this.data.attributes.radius, startAngle, endAngle, counterClockwise);
669        this.data.attributes.context.lineWidth = this.data.attributes.line_width;
670
671        // line color
672        this.data.attributes.context.strokeStyle = color;
673        this.data.attributes.context.stroke();
674    };
675
676    TC_Instance.prototype.radialFade = function(x, y, color, from, key) {
677        // TODO: Make fade_time option
678        var rgb = hexToRgb(color);
679        var _this = this; // We have a few inner scopes here that will need access to our instance
680
681        var step = 0.2 * ((from === 1) ? -1 : 1);
682        var i;
683        for (i = 0; from <= 1 && from >= 0; i++) {
684            // Create inner scope so our variables are not changed by the time the Timeout triggers
685            (function() {
686                var delay = 50 * i;
687                var rgba = '';
688                if (rgb) {
689	                rgba = "rgba(" + rgb.r + ", " + rgb.g + ", " + rgb.b + ", " + (Math.round(from * 10) / 10) + ")";
690                }
691                useWindow.setTimeout(function() {
692                    _this.drawArc(x, y, rgba, 1);
693                }, delay);
694            }());
695            from += step;
696        }
697        if (typeof key !== undefined) {
698            useWindow.setTimeout(function() {
699                _this.data.state.fading[key] = false;
700            }, 50 * i);
701        }
702    };
703
704    TC_Instance.prototype.timeLeft = function() {
705        if (this.data.paused && typeof this.data.timer === "number") {
706            return this.data.timer;
707        }
708        var now = new Date();
709        return ((this.data.attributes.ref_date - now) / 1000);
710    };
711
712    TC_Instance.prototype.start = function() {
713        useWindow.cancelAnimationFrame(this.data.animation_frame);
714        useWindow.clearTimeout(this.data.animation_frame);
715
716        // Check if a date was passed in html attribute or jquery data
717        var attr_data_date = $(this.element).data('date');
718        if (typeof attr_data_date === "undefined") {
719            attr_data_date = $(this.element).attr('data-date');
720        }
721        if (typeof attr_data_date === "string") {
722            this.data.attributes.ref_date = parse_date(attr_data_date);
723        }
724        // Check if this is an unpause of a timer
725        else if (typeof this.data.timer === "number") {
726            if (this.data.paused) {
727                this.data.attributes.ref_date = (new Date()).getTime() + (this.data.timer * 1000);
728            }
729        }
730        else {
731            // Try to get data-timer
732            var attr_data_timer = $(this.element).data('timer');
733            if (typeof attr_data_timer === "undefined") {
734                attr_data_timer = $(this.element).attr('data-timer');
735            }
736            if (typeof attr_data_timer === "string") {
737                attr_data_timer = parseFloat(attr_data_timer);
738            }
739            if (typeof attr_data_timer === "number") {
740                this.data.timer = attr_data_timer;
741
742                this.data.attributes.ref_date = (new Date()).getTime() + (attr_data_timer * 1000);
743                if(this.config['ycd-count-up-from-end-date'] && this.config['countdownExpired']) {
744                    this.data.attributes.ref_date = new Date(moment(this.config['ycd-date-time-picker']).tz(this.config['ycd-time-zone']).format('MM/DD/YYYY H:m:s')).getTime() + (attr_data_timer * 1000);
745                }
746            }
747            else {
748                // data-timer and data-date were both not set
749                // use config date
750                this.data.attributes.ref_date = this.config.ref_date;
751            }
752        }
753
754        // Start running
755        this.data.paused = false;
756        this.update.call(this);
757    };
758
759    TC_Instance.prototype.restart = function() {
760        this.data.timer = false;
761        this.start();
762    };
763
764    TC_Instance.prototype.stop = function() {
765        if (typeof this.data.timer === "number") {
766            this.data.timer = this.timeLeft(this);
767        }
768        // Stop running
769        this.data.paused = true;
770        useWindow.cancelAnimationFrame(this.data.animation_frame);
771    };
772
773    TC_Instance.prototype.destroy = function() {
774        this.clearListeners();
775        this.stop();
776        useWindow.clearInterval(this.data.interval_fallback);
777        this.data.interval_fallback = null;
778        
779        this.container.remove();
780        $(this.element).removeAttr('data-tc-id');
781        $(this.element).removeData('tc-id');
782    };
783
784    TC_Instance.prototype.setOptions = function(options) {
785        if (this.config === null) {
786            this.default_options.ref_date = new Date();
787            this.config = $.extend(true, {}, this.default_options);
788        }
789        $.extend(true, this.config, options);
790
791        // Use window.top if use_top_frame is true
792        if(this.config.use_top_frame) {
793            useWindow = window.top;
794        }
795        else {
796            useWindow = window;
797        }
798        updateUsedWindow();
799        
800        this.data.total_duration = this.config.total_duration;
801        if (typeof this.data.total_duration === "string") {
802            if (typeof secondsIn[this.data.total_duration] !== "undefined") {
803                // If set to Years, Months, Days, Hours or Minutes, fetch the secondsIn value for that
804                this.data.total_duration = secondsIn[this.data.total_duration];
805            }
806            else if (this.data.total_duration === "Auto") {
807                // If set to auto, total_duration is the size of 1 unit, of the unit type bigger than the largest shown
808                for(var i = 0; i < Object.keys(this.config.time).length; i++) {
809                    var unit = Object.keys(this.config.time)[i];
810                    if (this.config.time[unit].show) {
811                        this.data.total_duration = secondsIn[nextUnits[unit]];
812                        break;
813                    }
814                }
815            }
816            else {
817                // If it's a string, but neither of the above, user screwed up.
818                this.data.total_duration = secondsIn["Years"];
819                console.error("Valid values for TimeCircles config.total_duration are either numeric, or (string) Years, Months, Days, Hours, Minutes, Auto");
820            }
821        }
822    };
823
824    TC_Instance.prototype.addListener = function(f, context, type) {
825        if (typeof f !== "function")
826            return;
827        if (typeof type === "undefined")
828            type = "visible";
829        this.listeners[type].push({func: f, scope: context});
830    };
831
832    TC_Instance.prototype.notifyListeners = function(unit, value, total, type) {
833        for (var i = 0; i < this.listeners[type].length; i++) {
834            var listener = this.listeners[type][i];
835            listener.func.apply(listener.scope, [unit, value, total]);
836        }
837    };
838
839    TC_Instance.prototype.default_options = {
840        ref_date: new Date(),
841        start: true,
842        animation: "smooth",
843        count_past_zero: true,
844        circle_bg_color: "#60686F",
845        use_background: true,
846        fg_width: 0.1,
847        bg_width: 1.2,
848        text_size: 0.07,
849        number_size: 0.28,
850        total_duration: "Auto",
851        direction: "Clockwise",
852        use_top_frame: false,
853        start_angle: 0,
854        time: {
855            Years: {
856                show: true,
857                text: "Years",
858                color: "#FC6"
859            },
860            Months: {
861                show: true,
862                text: "Months",
863                color: "#FC6"
864            },
865			Days: {
866                show: true,
867                text: "Days",
868                color: "#FC6"
869            },
870            Hours: {
871                show: true,
872                text: "Hours",
873                color: "#9CF"
874            },
875            Minutes: {
876                show: true,
877                text: "Minutes",
878                color: "#BFB"
879            },
880            Seconds: {
881                show: true,
882                text: "Seconds",
883                color: "#F99"
884            }
885        }
886    };
887
888    // Time circle class
889    var TC_Class = function(elements, options) {
890        this.elements = elements;
891        this.options = options;
892        this.foreach();
893    };
894
895    TC_Class.prototype.getInstance = function(element) {
896        var instance;
897
898        var cur_id = $(element).data("tc-id");
899        if (typeof cur_id === "undefined") {
900            cur_id = guid();
901            $(element).attr("data-tc-id", cur_id);
902        }
903        if (typeof TC_Instance_List[cur_id] === "undefined") {
904            var options = this.options;
905            var element_options = $(element).data('options');
906            if (typeof element_options === "string") {
907                element_options = JSON.parse(element_options);
908            }
909            if (typeof element_options === "object") {
910                options = $.extend(true, {}, this.options, element_options);
911            }
912            instance = new TC_Instance(element, options);
913            TC_Instance_List[cur_id] = instance;
914        }
915        else {
916            instance = TC_Instance_List[cur_id];
917            if (typeof this.options !== "undefined") {
918                instance.setOptions(this.options);
919            }
920        }
921        return instance;
922    };
923
924    TC_Class.prototype.addTime = function(seconds_to_add) {
925        this.foreach(function(instance) {
926            instance.addTime(seconds_to_add);
927        });
928    };
929    
930    TC_Class.prototype.foreach = function(callback) {
931        var _this = this;
932        this.elements.each(function() {
933            var instance = _this.getInstance(this);
934            if (typeof callback === "function") {
935                callback(instance);
936            }
937        });
938        return this;
939    };
940
941    TC_Class.prototype.start = function() {
942        this.foreach(function(instance) {
943            instance.start();
944        });
945        return this;
946    };
947
948    TC_Class.prototype.stop = function() {
949        this.foreach(function(instance) {
950            instance.stop();
951        });
952        return this;
953    };
954
955    TC_Class.prototype.restart = function() {
956        this.foreach(function(instance) {
957            instance.restart();
958        });
959        return this;
960    };
961
962    TC_Class.prototype.rebuild = function() {
963        this.foreach(function(instance) {
964            instance.initialize(false);
965        });
966        return this;
967    };
968
969    TC_Class.prototype.getTime = function() {
970        return this.getInstance(this.elements[0]).timeLeft();
971    };
972
973    TC_Class.prototype.addListener = function(f, type) {
974        if (typeof type === "undefined")
975            type = "visible";
976        var _this = this;
977        this.foreach(function(instance) {
978            instance.addListener(f, _this.elements, type);
979        });
980        return this;
981    };
982
983    TC_Class.prototype.destroy = function() {
984        this.foreach(function(instance) {
985            instance.destroy();
986        });
987        return this;
988    };
989
990    TC_Class.prototype.end = function() {
991        return this.elements;
992    };
993
994    $.fn.TimeCircles = function(options) {
995        return new TC_Class(this, options);
996    };
997}(jQuery));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.