showoff 0.14.3 → 0.15.0

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