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