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