TimeCircles

https://github.com/wimbarelds/TimeCircles

This script should not be not be installed directly. It is a library for other scripts to include with the meta directive // @require https://update.greatest.deepsurf.us/scripts/12377/73868/TimeCircles.js

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