vendor: 102,749 bytes, lines 1-3487
1/* 2 * qTip2 - Pretty powerful tooltips - v3.0.3 3 * http://qtip2.com 4 * 5 * Copyright (c) 2016 6 * Released under the MIT licenses 7 * http://jquery.org/license 8 * 9 * Date: Wed May 11 2016 10:31 GMT+0100+0100 10 * Plugins: tips modal viewport svg imagemap ie6 11 * Styles: core basic css3 12 */ 13/*global window: false, jQuery: false, console: false, define: false */ 14 15/* Cache window, document, undefined */ 16(function( window, document, undefined ) { 17 18// Uses AMD or browser globals to create a jQuery plugin. 19(function( factory ) { 20 "use strict"; 21 if(typeof define === 'function' && define.amd) { 22 define(['jquery'], factory); 23 } 24 else if(jQuery && !jQuery.fn.qtip) { 25 factory(jQuery); 26 } 27} 28(function($) { 29 "use strict"; // Enable ECMAScript "strict" operation for this function. See more: http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/ 30;// Munge the primitives - Paul Irish tip 31var TRUE = true, 32FALSE = false, 33NULL = null, 34 35// Common variables 36X = 'x', Y = 'y', 37WIDTH = 'width', 38HEIGHT = 'height', 39 40// Positioning sides 41TOP = 'top', 42LEFT = 'left', 43BOTTOM = 'bottom', 44RIGHT = 'right', 45CENTER = 'center', 46 47// Position adjustment types 48FLIP = 'flip', 49FLIPINVERT = 'flipinvert', 50SHIFT = 'shift', 51 52// Shortcut vars 53QTIP, PROTOTYPE, CORNER, CHECKS, 54PLUGINS = {}, 55NAMESPACE = 'qtip', 56ATTR_HAS = 'data-hasqtip', 57ATTR_ID = 'data-qtip-id', 58WIDGET = ['ui-widget', 'ui-tooltip'], 59SELECTOR = '.'+NAMESPACE, 60INACTIVE_EVENTS = 'click dblclick mousedown mouseup mousemove mouseleave mouseenter'.split(' '), 61 62CLASS_FIXED = NAMESPACE+'-fixed', 63CLASS_DEFAULT = NAMESPACE + '-default', 64CLASS_FOCUS = NAMESPACE + '-focus', 65CLASS_HOVER = NAMESPACE + '-hover', 66CLASS_DISABLED = NAMESPACE+'-disabled', 67 68replaceSuffix = '_replacedByqTip', 69oldtitle = 'oldtitle', 70trackingBound, 71 72// Browser detection 73BROWSER = { 74 /* 75 * IE version detection 76 * 77 * Adapted from: http://ajaxian.com/archives/attack-of-the-ie-conditional-comment 78 * Credit to James Padolsey for the original implemntation! 79 */ 80 ie: (function() { 81 /* eslint-disable no-empty */ 82 var v, i; 83 for ( 84 v = 4, i = document.createElement('div'); 85 (i.innerHTML = '<!--[if gt IE ' + v + ']><i></i><![endif]-->') && i.getElementsByTagName('i')[0]; 86 v+=1 87 ) {} 88 return v > 4 ? v : NaN; 89 /* eslint-enable no-empty */ 90 })(), 91 92 /* 93 * iOS version detection 94 */ 95 iOS: parseFloat( 96 ('' + (/CPU.*OS ([0-9_]{1,5})|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent) || [0,''])[1]) 97 .replace('undefined', '3_2').replace('_', '.').replace('_', '') 98 ) || FALSE 99}; 100;function QTip(target, options, id, attr) { 101 // Elements and ID 102 this.id = id; 103 this.target = target; 104 this.tooltip = NULL; 105 this.elements = { target: target }; 106 107 // Internal constructs 108 this._id = NAMESPACE + '-' + id; 109 this.timers = { img: {} }; 110 this.options = options; 111 this.plugins = {}; 112 113 // Cache object 114 this.cache = { 115 event: {}, 116 target: $(), 117 disabled: FALSE, 118 attr: attr, 119 onTooltip: FALSE, 120 lastClass: '' 121 }; 122 123 // Set the initial flags 124 this.rendered = this.destroyed = this.disabled = this.waiting = 125 this.hiddenDuringWait = this.positioning = this.triggering = FALSE; 126} 127PROTOTYPE = QTip.prototype; 128 129PROTOTYPE._when = function(deferreds) { 130 return $.when.apply($, deferreds); 131}; 132 133PROTOTYPE.render = function(show) { 134 if(this.rendered || this.destroyed) { return this; } // If tooltip has already been rendered, exit 135 136 var self = this, 137 options = this.options, 138 cache = this.cache, 139 elements = this.elements, 140 text = options.content.text, 141 title = options.content.title, 142 button = options.content.button, 143 posOptions = options.position, 144 deferreds = []; 145 146 // Add ARIA attributes to target 147 $.attr(this.target[0], 'aria-describedby', this._id); 148 149 // Create public position object that tracks current position corners 150 cache.posClass = this._createPosClass( 151 (this.position = { my: posOptions.my, at: posOptions.at }).my 152 ); 153 154 // Create tooltip element 155 this.tooltip = elements.tooltip = $('<div/>', { 156 'id': this._id, 157 'class': [ NAMESPACE, CLASS_DEFAULT, options.style.classes, cache.posClass ].join(' '), 158 'width': options.style.width || '', 159 'height': options.style.height || '', 160 'tracking': posOptions.target === 'mouse' && posOptions.adjust.mouse, 161 162 /* ARIA specific attributes */ 163 'role': 'alert', 164 'aria-live': 'polite', 165 'aria-atomic': FALSE, 166 'aria-describedby': this._id + '-content', 167 'aria-hidden': TRUE 168 }) 169 .toggleClass(CLASS_DISABLED, this.disabled) 170 .attr(ATTR_ID, this.id) 171 .data(NAMESPACE, this) 172 .appendTo(posOptions.container) 173 .append( 174 // Create content element 175 elements.content = $('<div />', { 176 'class': NAMESPACE + '-content', 177 'id': this._id + '-content', 178 'aria-atomic': TRUE 179 }) 180 ); 181 182 // Set rendered flag and prevent redundant reposition calls for now 183 this.rendered = -1; 184 this.positioning = TRUE; 185 186 // Create title... 187 if(title) { 188 this._createTitle(); 189 190 // Update title only if its not a callback (called in toggle if so) 191 if(!$.isFunction(title)) { 192 deferreds.push( this._updateTitle(title, FALSE) ); 193 } 194 } 195 196 // Create button 197 if(button) { this._createButton(); } 198 199 // Set proper rendered flag and update content if not a callback function (called in toggle) 200 if(!$.isFunction(text)) { 201 deferreds.push( this._updateContent(text, FALSE) ); 202 } 203 this.rendered = TRUE; 204 205 // Setup widget classes 206 this._setWidget(); 207 208 // Initialize 'render' plugins 209 $.each(PLUGINS, function(name) { 210 var instance; 211 if(this.initialize === 'render' && (instance = this(self))) { 212 self.plugins[name] = instance; 213 } 214 }); 215 216 // Unassign initial events and assign proper events 217 this._unassignEvents(); 218 this._assignEvents(); 219 220 // When deferreds have completed 221 this._when(deferreds).then(function() { 222 // tooltiprender event 223 self._trigger('render'); 224 225 // Reset flags 226 self.positioning = FALSE; 227 228 // Show tooltip if not hidden during wait period 229 if(!self.hiddenDuringWait && (options.show.ready || show)) { 230 self.toggle(TRUE, cache.event, FALSE); 231 } 232 self.hiddenDuringWait = FALSE; 233 }); 234 235 // Expose API 236 QTIP.api[this.id] = this; 237 238 return this; 239}; 240 241PROTOTYPE.destroy = function(immediate) { 242 // Set flag the signify destroy is taking place to plugins 243 // and ensure it only gets destroyed once! 244 if(this.destroyed) { return this.target; } 245 246 function process() { 247 if(this.destroyed) { return; } 248 this.destroyed = TRUE; 249 250 var target = this.target, 251 title = target.attr(oldtitle), 252 timer; 253 254 // Destroy tooltip if rendered 255 if(this.rendered) { 256 this.tooltip.stop(1,0).find('*').remove().end().remove(); 257 } 258 259 // Destroy all plugins 260 $.each(this.plugins, function() { 261 this.destroy && this.destroy(); 262 }); 263 264 // Clear timers 265 for (timer in this.timers) { 266 if (this.timers.hasOwnProperty(timer)) { 267 clearTimeout(this.timers[timer]); 268 } 269 } 270 271 // Remove api object and ARIA attributes 272 target.removeData(NAMESPACE) 273 .removeAttr(ATTR_ID) 274 .removeAttr(ATTR_HAS) 275 .removeAttr('aria-describedby'); 276 277 // Reset old title attribute if removed 278 if(this.options.suppress && title) { 279 target.attr('title', title).removeAttr(oldtitle); 280 } 281 282 // Remove qTip events associated with this API 283 this._unassignEvents(); 284 285 // Remove ID from used id objects, and delete object references 286 // for better garbage collection and leak protection 287 this.options = this.elements = this.cache = this.timers = 288 this.plugins = this.mouse = NULL; 289 290 // Delete epoxsed API object 291 delete QTIP.api[this.id]; 292 } 293 294 // If an immediate destroy is needed 295 if((immediate !== TRUE || this.triggering === 'hide') && this.rendered) { 296 this.tooltip.one('tooltiphidden', $.proxy(process, this)); 297 !this.triggering && this.hide(); 298 } 299 300 // If we're not in the process of hiding... process 301 else { process.call(this); } 302 303 return this.target; 304}; 305;function invalidOpt(a) { 306 return a === NULL || $.type(a) !== 'object'; 307} 308 309function invalidContent(c) { 310 return !($.isFunction(c) || 311 c && c.attr || 312 c.length || 313 $.type(c) === 'object' && (c.jquery || c.then)); 314} 315 316// Option object sanitizer 317function sanitizeOptions(opts) { 318 var content, text, ajax, once; 319 320 if(invalidOpt(opts)) { return FALSE; } 321 322 if(invalidOpt(opts.metadata)) { 323 opts.metadata = { type: opts.metadata }; 324 } 325 326 if('content' in opts) { 327 content = opts.content; 328 329 if(invalidOpt(content) || content.jquery || content.done) { 330 text = invalidContent(content) ? FALSE : content; 331 content = opts.content = { 332 text: text 333 }; 334 } 335 else { text = content.text; } 336 337 // DEPRECATED - Old content.ajax plugin functionality 338 // Converts it into the proper Deferred syntax 339 if('ajax' in content) { 340 ajax = content.ajax; 341 once = ajax && ajax.once !== FALSE; 342 delete content.ajax; 343 344 content.text = function(event, api) { 345 var loading = text || $(this).attr(api.options.content.attr) || 'Loading...', 346 347 deferred = $.ajax( 348 $.extend({}, ajax, { context: api }) 349 ) 350 .then(ajax.success, NULL, ajax.error) 351 .then(function(newContent) { 352 if(newContent && once) { api.set('content.text', newContent); } 353 return newContent; 354 }, 355 function(xhr, status, error) { 356 if(api.destroyed || xhr.status === 0) { return; } 357 api.set('content.text', status + ': ' + error); 358 }); 359 360 return !once ? (api.set('content.text', loading), deferred) : loading; 361 }; 362 } 363 364 if('title' in content) { 365 if($.isPlainObject(content.title)) { 366 content.button = content.title.button; 367 content.title = content.title.text; 368 } 369 370 if(invalidContent(content.title || FALSE)) { 371 content.title = FALSE; 372 } 373 } 374 } 375 376 if('position' in opts && invalidOpt(opts.position)) { 377 opts.position = { my: opts.position, at: opts.position }; 378 } 379 380 if('show' in opts && invalidOpt(opts.show)) { 381 opts.show = opts.show.jquery ? { target: opts.show } : 382 opts.show === TRUE ? { ready: TRUE } : { event: opts.show }; 383 } 384 385 if('hide' in opts && invalidOpt(opts.hide)) { 386 opts.hide = opts.hide.jquery ? { target: opts.hide } : { event: opts.hide }; 387 } 388 389 if('style' in opts && invalidOpt(opts.style)) { 390 opts.style = { classes: opts.style }; 391 } 392 393 // Sanitize plugin options 394 $.each(PLUGINS, function() { 395 this.sanitize && this.sanitize(opts); 396 }); 397 398 return opts; 399} 400 401// Setup builtin .set() option checks 402CHECKS = PROTOTYPE.checks = { 403 builtin: { 404 // Core checks 405 '^id$': function(obj, o, v, prev) { 406 var id = v === TRUE ? QTIP.nextid : v, 407 newId = NAMESPACE + '-' + id; 408 409 if(id !== FALSE && id.length > 0 && !$('#'+newId).length) { 410 this._id = newId; 411 412 if(this.rendered) { 413 this.tooltip[0].id = this._id; 414 this.elements.content[0].id = this._id + '-content'; 415 this.elements.title[0].id = this._id + '-title'; 416 } 417 } 418 else { obj[o] = prev; } 419 }, 420 '^prerender': function(obj, o, v) { 421 v && !this.rendered && this.render(this.options.show.ready); 422 }, 423 424 // Content checks 425 '^content.text$': function(obj, o, v) { 426 this._updateContent(v); 427 }, 428 '^content.attr$': function(obj, o, v, prev) { 429 if(this.options.content.text === this.target.attr(prev)) { 430 this._updateContent( this.target.attr(v) ); 431 } 432 }, 433 '^content.title$': function(obj, o, v) { 434 // Remove title if content is null 435 if(!v) { return this._removeTitle(); } 436 437 // If title isn't already created, create it now and update 438 v && !this.elements.title && this._createTitle(); 439 this._updateTitle(v); 440 }, 441 '^content.button$': function(obj, o, v) { 442 this._updateButton(v); 443 }, 444 '^content.title.(text|button)$': function(obj, o, v) { 445 this.set('content.'+o, v); // Backwards title.text/button compat 446 }, 447 448 // Position checks 449 '^position.(my|at)$': function(obj, o, v){ 450 if('string' === typeof v) { 451 this.position[o] = obj[o] = new CORNER(v, o === 'at'); 452 } 453 }, 454 '^position.container$': function(obj, o, v){ 455 this.rendered && this.tooltip.appendTo(v); 456 }, 457 458 // Show checks 459 '^show.ready$': function(obj, o, v) { 460 v && (!this.rendered && this.render(TRUE) || this.toggle(TRUE)); 461 }, 462 463 // Style checks 464 '^style.classes$': function(obj, o, v, p) { 465 this.rendered && this.tooltip.removeClass(p).addClass(v); 466 }, 467 '^style.(width|height)': function(obj, o, v) { 468 this.rendered && this.tooltip.css(o, v); 469 }, 470 '^style.widget|content.title': function() { 471 this.rendered && this._setWidget(); 472 }, 473 '^style.def': function(obj, o, v) { 474 this.rendered && this.tooltip.toggleClass(CLASS_DEFAULT, !!v); 475 }, 476 477 // Events check 478 '^events.(render|show|move|hide|focus|blur)$': function(obj, o, v) { 479 this.rendered && this.tooltip[($.isFunction(v) ? '' : 'un') + 'bind']('tooltip'+o, v); 480 }, 481 482 // Properties which require event reassignment 483 '^(show|hide|position).(event|target|fixed|inactive|leave|distance|viewport|adjust)': function() { 484 if(!this.rendered) { return; } 485 486 // Set tracking flag 487 var posOptions = this.options.position; 488 this.tooltip.attr('tracking', posOptions.target === 'mouse' && posOptions.adjust.mouse); 489 490 // Reassign events 491 this._unassignEvents(); 492 this._assignEvents(); 493 } 494 } 495}; 496 497// Dot notation converter 498function convertNotation(options, notation) { 499 var i = 0, obj, option = options, 500 501 // Split notation into array 502 levels = notation.split('.'); 503 504 // Loop through 505 while(option = option[ levels[i++] ]) { 506 if(i < levels.length) { obj = option; } 507 } 508 509 return [obj || options, levels.pop()]; 510} 511 512PROTOTYPE.get = function(notation) { 513 if(this.destroyed) { return this; } 514 515 var o = convertNotation(this.options, notation.toLowerCase()), 516 result = o[0][ o[1] ]; 517 518 return result.precedance ? result.string() : result; 519}; 520 521function setCallback(notation, args) { 522 var category, rule, match; 523 524 for(category in this.checks) { 525 if (!this.checks.hasOwnProperty(category)) { continue; } 526 527 for(rule in this.checks[category]) { 528 if (!this.checks[category].hasOwnProperty(rule)) { continue; } 529 530 if(match = (new RegExp(rule, 'i')).exec(notation)) { 531 args.push(match); 532 533 if(category === 'builtin' || this.plugins[category]) { 534 this.checks[category][rule].apply( 535 this.plugins[category] || this, args 536 ); 537 } 538 } 539 } 540 } 541} 542 543var rmove = /^position\.(my|at|adjust|target|container|viewport)|style|content|show\.ready/i, 544 rrender = /^prerender|show\.ready/i; 545 546PROTOTYPE.set = function(option, value) { 547 if(this.destroyed) { return this; } 548 549 var rendered = this.rendered, 550 reposition = FALSE, 551 options = this.options, 552 name; 553 554 // Convert singular option/value pair into object form 555 if('string' === typeof option) { 556 name = option; option = {}; option[name] = value; 557 } 558 else { option = $.extend({}, option); } 559 560 // Set all of the defined options to their new values 561 $.each(option, function(notation, val) { 562 if(rendered && rrender.test(notation)) { 563 delete option[notation]; return; 564 } 565 566 // Set new obj value 567 var obj = convertNotation(options, notation.toLowerCase()), previous; 568 previous = obj[0][ obj[1] ]; 569 obj[0][ obj[1] ] = val && val.nodeType ? $(val) : val; 570 571 // Also check if we need to reposition 572 reposition = rmove.test(notation) || reposition; 573 574 // Set the new params for the callback 575 option[notation] = [obj[0], obj[1], val, previous]; 576 }); 577 578 // Re-sanitize options 579 sanitizeOptions(options); 580 581 /* 582 * Execute any valid callbacks for the set options 583 * Also set positioning flag so we don't get loads of redundant repositioning calls. 584 */ 585 this.positioning = TRUE; 586 $.each(option, $.proxy(setCallback, this)); 587 this.positioning = FALSE; 588 589 // Update position if needed 590 if(this.rendered && this.tooltip[0].offsetWidth > 0 && reposition) { 591 this.reposition( options.position.target === 'mouse' ? NULL : this.cache.event ); 592 } 593 594 return this; 595}; 596;PROTOTYPE._update = function(content, element) { 597 var self = this, 598 cache = this.cache; 599 600 // Make sure tooltip is rendered and content is defined. If not return 601 if(!this.rendered || !content) { return FALSE; } 602 603 // Use function to parse content 604 if($.isFunction(content)) { 605 content = content.call(this.elements.target, cache.event, this) || ''; 606 } 607 608 // Handle deferred content 609 if($.isFunction(content.then)) { 610 cache.waiting = TRUE; 611 return content.then(function(c) { 612 cache.waiting = FALSE; 613 return self._update(c, element); 614 }, NULL, function(e) { 615 return self._update(e, element); 616 }); 617 } 618 619 // If content is null... return false 620 if(content === FALSE || !content && content !== '') { return FALSE; } 621 622 // Append new content if its a DOM array and show it if hidden 623 if(content.jquery && content.length > 0) { 624 element.empty().append( 625 content.css({ display: 'block', visibility: 'visible' }) 626 ); 627 } 628 629 // Content is a regular string, insert the new content 630 else { element.html(content); } 631 632 // Wait for content to be loaded, and reposition 633 return this._waitForContent(element).then(function(images) { 634 if(self.rendered && self.tooltip[0].offsetWidth > 0) { 635 self.reposition(cache.event, !images.length); 636 } 637 }); 638}; 639 640PROTOTYPE._waitForContent = function(element) { 641 var cache = this.cache; 642 643 // Set flag 644 cache.waiting = TRUE; 645 646 // If imagesLoaded is included, ensure images have loaded and return promise 647 return ( $.fn.imagesLoaded ? element.imagesLoaded() : new $.Deferred().resolve([]) ) 648 .done(function() { cache.waiting = FALSE; }) 649 .promise(); 650}; 651 652PROTOTYPE._updateContent = function(content, reposition) { 653 this._update(content, this.elements.content, reposition); 654}; 655 656PROTOTYPE._updateTitle = function(content, reposition) { 657 if(this._update(content, this.elements.title, reposition) === FALSE) { 658 this._removeTitle(FALSE); 659 } 660}; 661 662PROTOTYPE._createTitle = function() 663{ 664 var elements = this.elements, 665 id = this._id+'-title'; 666 667 // Destroy previous title element, if present 668 if(elements.titlebar) { this._removeTitle(); } 669 670 // Create title bar and title elements 671 elements.titlebar = $('<div />', { 672 'class': NAMESPACE + '-titlebar ' + (this.options.style.widget ? createWidgetClass('header') : '') 673 }) 674 .append( 675 elements.title = $('<div />', { 676 'id': id, 677 'class': NAMESPACE + '-title', 678 'aria-atomic': TRUE 679 }) 680 ) 681 .insertBefore(elements.content) 682 683 // Button-specific events 684 .delegate('.qtip-close', 'mousedown keydown mouseup keyup mouseout', function(event) { 685 $(this).toggleClass('ui-state-active ui-state-focus', event.type.substr(-4) === 'down'); 686 }) 687 .delegate('.qtip-close', 'mouseover mouseout', function(event){ 688 $(this).toggleClass('ui-state-hover', event.type === 'mouseover'); 689 }); 690 691 // Create button if enabled 692 if(this.options.content.button) { this._createButton(); } 693}; 694 695PROTOTYPE._removeTitle = function(reposition) 696{ 697 var elements = this.elements; 698 699 if(elements.title) { 700 elements.titlebar.remove(); 701 elements.titlebar = elements.title = elements.button = NULL; 702 703 // Reposition if enabled 704 if(reposition !== FALSE) { this.reposition(); } 705 } 706}; 707;PROTOTYPE._createPosClass = function(my) { 708 return NAMESPACE + '-pos-' + (my || this.options.position.my).abbrev(); 709}; 710 711PROTOTYPE.reposition = function(event, effect) { 712 if(!this.rendered || this.positioning || this.destroyed) { return this; } 713 714 // Set positioning flag 715 this.positioning = TRUE; 716 717 var cache = this.cache, 718 tooltip = this.tooltip, 719 posOptions = this.options.position, 720 target = posOptions.target, 721 my = posOptions.my, 722 at = posOptions.at, 723 viewport = posOptions.viewport, 724 container = posOptions.container, 725 adjust = posOptions.adjust, 726 method = adjust.method.split(' '), 727 tooltipWidth = tooltip.outerWidth(FALSE), 728 tooltipHeight = tooltip.outerHeight(FALSE), 729 targetWidth = 0, 730 targetHeight = 0, 731 type = tooltip.css('position'), 732 position = { left: 0, top: 0 }, 733 visible = tooltip[0].offsetWidth > 0, 734 isScroll = event && event.type === 'scroll', 735 win = $(window), 736 doc = container[0].ownerDocument, 737 mouse = this.mouse, 738 pluginCalculations, offset, adjusted, newClass; 739 740 // Check if absolute position was passed 741 if($.isArray(target) && target.length === 2) { 742 // Force left top and set position 743 at = { x: LEFT, y: TOP }; 744 position = { left: target[0], top: target[1] }; 745 } 746 747 // Check if mouse was the target 748 else if(target === 'mouse') { 749 // Force left top to allow flipping 750 at = { x: LEFT, y: TOP }; 751 752 // Use the mouse origin that caused the show event, if distance hiding is enabled 753 if((!adjust.mouse || this.options.hide.distance) && cache.origin && cache.origin.pageX) { 754 event = cache.origin; 755 } 756 757 // Use cached event for resize/scroll events 758 else if(!event || event && (event.type === 'resize' || event.type === 'scroll')) { 759 event = cache.event; 760 } 761 762 // Otherwise, use the cached mouse coordinates if available 763 else if(mouse && mouse.pageX) { 764 event = mouse; 765 } 766 767 // Calculate body and container offset and take them into account below 768 if(type !== 'static') { position = container.offset(); } 769 if(doc.body.offsetWidth !== (window.innerWidth || doc.documentElement.clientWidth)) { 770 offset = $(document.body).offset(); 771 } 772 773 // Use event coordinates for position 774 position = { 775 left: event.pageX - position.left + (offset && offset.left || 0), 776 top: event.pageY - position.top + (offset && offset.top || 0) 777 }; 778 779 // Scroll events are a pain, some browsers 780 if(adjust.mouse && isScroll && mouse) { 781 position.left -= (mouse.scrollX || 0) - win.scrollLeft(); 782 position.top -= (mouse.scrollY || 0) - win.scrollTop(); 783 } 784 } 785 786 // Target wasn't mouse or absolute... 787 else { 788 // Check if event targetting is being used 789 if(target === 'event') { 790 if(event && event.target && event.type !== 'scroll' && event.type !== 'resize') { 791 cache.target = $(event.target); 792 } 793 else if(!event.target) { 794 cache.target = this.elements.target; 795 } 796 } 797 else if(target !== 'event'){ 798 cache.target = $(target.jquery ? target : this.elements.target); 799 } 800 target = cache.target; 801 802 // Parse the target into a jQuery object and make sure there's an element present 803 target = $(target).eq(0); 804 if(target.length === 0) { return this; } 805 806 // Check if window or document is the target 807 else if(target[0] === document || target[0] === window) { 808 targetWidth = BROWSER.iOS ? window.innerWidth : target.width(); 809 targetHeight = BROWSER.iOS ? window.innerHeight : target.height(); 810 811 if(target[0] === window) { 812 position = { 813 top: (viewport || target).scrollTop(), 814 left: (viewport || target).scrollLeft() 815 }; 816 } 817 } 818 819 // Check if the target is an <AREA> element 820 else if(PLUGINS.imagemap && target.is('area')) { 821 pluginCalculations = PLUGINS.imagemap(this, target, at, PLUGINS.viewport ? method : FALSE); 822 } 823 824 // Check if the target is an SVG element 825 else if(PLUGINS.svg && target && target[0].ownerSVGElement) { 826 pluginCalculations = PLUGINS.svg(this, target, at, PLUGINS.viewport ? method : FALSE); 827 } 828 829 // Otherwise use regular jQuery methods 830 else { 831 targetWidth = target.outerWidth(FALSE); 832 targetHeight = target.outerHeight(FALSE); 833 position = target.offset(); 834 } 835 836 // Parse returned plugin values into proper variables 837 if(pluginCalculations) { 838 targetWidth = pluginCalculations.width; 839 targetHeight = pluginCalculations.height; 840 offset = pluginCalculations.offset; 841 position = pluginCalculations.position; 842 } 843 844 // Adjust position to take into account offset parents 845 position = this.reposition.offset(target, position, container); 846 847 // Adjust for position.fixed tooltips (and also iOS scroll bug in v3.2-4.0 & v4.3-4.3.2) 848 if(BROWSER.iOS > 3.1 && BROWSER.iOS < 4.1 || 849 BROWSER.iOS >= 4.3 && BROWSER.iOS < 4.33 || 850 !BROWSER.iOS && type === 'fixed' 851 ){ 852 position.left -= win.scrollLeft(); 853 position.top -= win.scrollTop(); 854 } 855 856 // Adjust position relative to target 857 if(!pluginCalculations || pluginCalculations && pluginCalculations.adjustable !== FALSE) { 858 position.left += at.x === RIGHT ? targetWidth : at.x === CENTER ? targetWidth / 2 : 0; 859 position.top += at.y === BOTTOM ? targetHeight : at.y === CENTER ? targetHeight / 2 : 0; 860 } 861 } 862 863 // Adjust position relative to tooltip 864 position.left += adjust.x + (my.x === RIGHT ? -tooltipWidth : my.x === CENTER ? -tooltipWidth / 2 : 0); 865 position.top += adjust.y + (my.y === BOTTOM ? -tooltipHeight : my.y === CENTER ? -tooltipHeight / 2 : 0); 866 867 // Use viewport adjustment plugin if enabled 868 if(PLUGINS.viewport) { 869 adjusted = position.adjusted = PLUGINS.viewport( 870 this, position, posOptions, targetWidth, targetHeight, tooltipWidth, tooltipHeight 871 ); 872 873 // Apply offsets supplied by positioning plugin (if used) 874 if(offset && adjusted.left) { position.left += offset.left; } 875 if(offset && adjusted.top) { position.top += offset.top; } 876 877 // Apply any new 'my' position 878 if(adjusted.my) { this.position.my = adjusted.my; } 879 } 880 881 // Viewport adjustment is disabled, set values to zero 882 else { position.adjusted = { left: 0, top: 0 }; } 883 884 // Set tooltip position class if it's changed 885 if(cache.posClass !== (newClass = this._createPosClass(this.position.my))) { 886 cache.posClass = newClass; 887 tooltip.removeClass(cache.posClass).addClass(newClass); 888 } 889 890 // tooltipmove event 891 if(!this._trigger('move', [position, viewport.elem || viewport], event)) { return this; } 892 delete position.adjusted; 893 894 // If effect is disabled, target it mouse, no animation is defined or positioning gives NaN out, set CSS directly 895 if(effect === FALSE || !visible || isNaN(position.left) || isNaN(position.top) || target === 'mouse' || !$.isFunction(posOptions.effect)) { 896 tooltip.css(position); 897 } 898 899 // Use custom function if provided 900 else if($.isFunction(posOptions.effect)) { 901 posOptions.effect.call(tooltip, this, $.extend({}, position)); 902 tooltip.queue(function(next) { 903 // Reset attributes to avoid cross-browser rendering bugs 904 $(this).css({ opacity: '', height: '' }); 905 if(BROWSER.ie) { this.style.removeAttribute('filter'); } 906 907 next(); 908 }); 909 } 910 911 // Set positioning flag 912 this.positioning = FALSE; 913 914 return this; 915}; 916 917// Custom (more correct for qTip!) offset calculator 918PROTOTYPE.reposition.offset = function(elem, pos, container) { 919 if(!container[0]) { return pos; } 920 921 var ownerDocument = $(elem[0].ownerDocument), 922 quirks = !!BROWSER.ie && document.compatMode !== 'CSS1Compat', 923 parent = container[0], 924 scrolled, position, parentOffset, overflow; 925 926 function scroll(e, i) { 927 pos.left += i * e.scrollLeft(); 928 pos.top += i * e.scrollTop(); 929 } 930 931 // Compensate for non-static containers offset 932 do { 933 if((position = $.css(parent, 'position')) !== 'static') { 934 if(position === 'fixed') { 935 parentOffset = parent.getBoundingClientRect(); 936 scroll(ownerDocument, -1); 937 } 938 else { 939 parentOffset = $(parent).position(); 940 parentOffset.left += parseFloat($.css(parent, 'borderLeftWidth')) || 0; 941 parentOffset.top += parseFloat($.css(parent, 'borderTopWidth')) || 0; 942 } 943 944 pos.left -= parentOffset.left + (parseFloat($.css(parent, 'marginLeft')) || 0); 945 pos.top -= parentOffset.top + (parseFloat($.css(parent, 'marginTop')) || 0); 946 947 // If this is the first parent element with an overflow of "scroll" or "auto", store it 948 if(!scrolled && (overflow = $.css(parent, 'overflow')) !== 'hidden' && overflow !== 'visible') { scrolled = $(parent); } 949 } 950 } 951 while(parent = parent.offsetParent); 952 953 // Compensate for containers scroll if it also has an offsetParent (or in IE quirks mode) 954 if(scrolled && (scrolled[0] !== ownerDocument[0] || quirks)) { 955 scroll(scrolled, 1); 956 } 957 958 return pos; 959}; 960 961// Corner class 962var C = (CORNER = PROTOTYPE.reposition.Corner = function(corner, forceY) { 963 corner = ('' + corner).replace(/([A-Z])/, ' $1').replace(/middle/gi, CENTER).toLowerCase(); 964 this.x = (corner.match(/left|right/i) || corner.match(/center/) || ['inherit'])[0].toLowerCase(); 965 this.y = (corner.match(/top|bottom|center/i) || ['inherit'])[0].toLowerCase(); 966 this.forceY = !!forceY; 967 968 var f = corner.charAt(0); 969 this.precedance = f === 't' || f === 'b' ? Y : X; 970}).prototype; 971 972C.invert = function(z, center) { 973 this[z] = this[z] === LEFT ? RIGHT : this[z] === RIGHT ? LEFT : center || this[z]; 974}; 975 976C.string = function(join) { 977 var x = this.x, y = this.y; 978 979 var result = x !== y ? 980 x === 'center' || y !== 'center' && (this.precedance === Y || this.forceY) ? 981 [y,x] : 982 [x,y] : 983 [x]; 984 985 return join !== false ? result.join(' ') : result; 986}; 987 988C.abbrev = function() { 989 var result = this.string(false); 990 return result[0].charAt(0) + (result[1] && result[1].charAt(0) || ''); 991}; 992 993C.clone = function() { 994 return new CORNER( this.string(), this.forceY ); 995}; 996 997; 998PROTOTYPE.toggle = function(state, event) { 999 var cache = this.cache, 1000 options = this.options, 1001 tooltip = this.tooltip; 1002 1003 // Try to prevent flickering when tooltip overlaps show element 1004 if(event) { 1005 if((/over|enter/).test(event.type) && cache.event && (/out|leave/).test(cache.event.type) && 1006 options.show.target.add(event.target).length === options.show.target.length && 1007 tooltip.has(event.relatedTarget).length) { 1008 return this; 1009 } 1010 1011 // Cache event 1012 cache.event = $.event.fix(event); 1013 } 1014 1015 // If we're currently waiting and we've just hidden... stop it 1016 this.waiting && !state && (this.hiddenDuringWait = TRUE); 1017 1018 // Render the tooltip if showing and it isn't already 1019 if(!this.rendered) { return state ? this.render(1) : this; } 1020 else if(this.destroyed || this.disabled) { return this; } 1021 1022 var type = state ? 'show' : 'hide', 1023 opts = this.options[type], 1024 posOptions = this.options.position, 1025 contentOptions = this.options.content, 1026 width = this.tooltip.css('width'), 1027 visible = this.tooltip.is(':visible'), 1028 animate = state || opts.target.length === 1, 1029 sameTarget = !event || opts.target.length < 2 || cache.target[0] === event.target, 1030 identicalState, allow, after; 1031 1032 // Detect state if valid one isn't provided 1033 if((typeof state).search('boolean|number')) { state = !visible; } 1034 1035 // Check if the tooltip is in an identical state to the new would-be state 1036 identicalState = !tooltip.is(':animated') && visible === state && sameTarget; 1037 1038 // Fire tooltip(show/hide) event and check if destroyed 1039 allow = !identicalState ? !!this._trigger(type, [90]) : NULL; 1040 1041 // Check to make sure the tooltip wasn't destroyed in the callback 1042 if(this.destroyed) { return this; } 1043 1044 // If the user didn't stop the method prematurely and we're showing the tooltip, focus it 1045 if(allow !== FALSE && state) { this.focus(event); } 1046 1047 // If the state hasn't changed or the user stopped it, return early 1048 if(!allow || identicalState) { return this; } 1049 1050 // Set ARIA hidden attribute 1051 $.attr(tooltip[0], 'aria-hidden', !!!state); 1052 1053 // Execute state specific properties 1054 if(state) { 1055 // Store show origin coordinates 1056 this.mouse && (cache.origin = $.event.fix(this.mouse)); 1057 1058 // Update tooltip content & title if it's a dynamic function 1059 if($.isFunction(contentOptions.text)) { this._updateContent(contentOptions.text, FALSE); } 1060 if($.isFunction(contentOptions.title)) { this._updateTitle(contentOptions.title, FALSE); } 1061 1062 // Cache mousemove events for positioning purposes (if not already tracking) 1063 if(!trackingBound && posOptions.target === 'mouse' && posOptions.adjust.mouse) { 1064 $(document).bind('mousemove.'+NAMESPACE, this._storeMouse); 1065 trackingBound = TRUE; 1066 } 1067 1068 // Update the tooltip position (set width first to prevent viewport/max-width issues) 1069 if(!width) { tooltip.css('width', tooltip.outerWidth(FALSE)); } 1070 this.reposition(event, arguments[2]); 1071 if(!width) { tooltip.css('width', ''); } 1072 1073 // Hide other tooltips if tooltip is solo 1074 if(!!opts.solo) { 1075 (typeof opts.solo === 'string' ? $(opts.solo) : $(SELECTOR, opts.solo)) 1076 .not(tooltip).not(opts.target).qtip('hide', new $.Event('tooltipsolo')); 1077 } 1078 } 1079 else { 1080 // Clear show timer if we're hiding 1081 clearTimeout(this.timers.show); 1082 1083 // Remove cached origin on hide 1084 delete cache.origin; 1085 1086 // Remove mouse tracking event if not needed (all tracking qTips are hidden) 1087 if(trackingBound && !$(SELECTOR+'[tracking="true"]:visible', opts.solo).not(tooltip).length) { 1088 $(document).unbind('mousemove.'+NAMESPACE); 1089 trackingBound = FALSE; 1090 } 1091 1092 // Blur the tooltip 1093 this.blur(event); 1094 } 1095 1096 // Define post-animation, state specific properties 1097 after = $.proxy(function() { 1098 if(state) { 1099 // Prevent antialias from disappearing in IE by removing filter 1100 if(BROWSER.ie) { tooltip[0].style.removeAttribute('filter'); } 1101 1102 // Remove overflow setting to prevent tip bugs 1103 tooltip.css('overflow', ''); 1104 1105 // Autofocus elements if enabled 1106 if('string' === typeof opts.autofocus) { 1107 $(this.options.show.autofocus, tooltip).focus(); 1108 } 1109 1110 // If set, hide tooltip when inactive for delay period 1111 this.options.show.target.trigger('qtip-'+this.id+'-inactive'); 1112 } 1113 else { 1114 // Reset CSS states 1115 tooltip.css({ 1116 display: '', 1117 visibility: '', 1118 opacity: '', 1119 left: '', 1120 top: '' 1121 }); 1122 } 1123 1124 // tooltipvisible/tooltiphidden events 1125 this._trigger(state ? 'visible' : 'hidden'); 1126 }, this); 1127 1128 // If no effect type is supplied, use a simple toggle 1129 if(opts.effect === FALSE || animate === FALSE) { 1130 tooltip[ type ](); 1131 after(); 1132 } 1133 1134 // Use custom function if provided 1135 else if($.isFunction(opts.effect)) { 1136 tooltip.stop(1, 1); 1137 opts.effect.call(tooltip, this); 1138 tooltip.queue('fx', function(n) { 1139 after(); n(); 1140 }); 1141 } 1142 1143 // Use basic fade function by default 1144 else { tooltip.fadeTo(90, state ? 1 : 0, after); } 1145 1146 // If inactive hide method is set, active it 1147 if(state) { opts.target.trigger('qtip-'+this.id+'-inactive'); } 1148 1149 return this; 1150}; 1151 1152PROTOTYPE.show = function(event) { return this.toggle(TRUE, event); }; 1153 1154PROTOTYPE.hide = function(event) { return this.toggle(FALSE, event); }; 1155;PROTOTYPE.focus = function(event) { 1156 if(!this.rendered || this.destroyed) { return this; } 1157 1158 var qtips = $(SELECTOR), 1159 tooltip = this.tooltip, 1160 curIndex = parseInt(tooltip[0].style.zIndex, 10), 1161 newIndex = QTIP.zindex + qtips.length; 1162 1163 // Only update the z-index if it has changed and tooltip is not already focused 1164 if(!tooltip.hasClass(CLASS_FOCUS)) { 1165 // tooltipfocus event 1166 if(this._trigger('focus', [newIndex], event)) { 1167 // Only update z-index's if they've changed 1168 if(curIndex !== newIndex) { 1169 // Reduce our z-index's and keep them properly ordered 1170 qtips.each(function() { 1171 if(this.style.zIndex > curIndex) { 1172 this.style.zIndex = this.style.zIndex - 1; 1173 } 1174 }); 1175 1176 // Fire blur event for focused tooltip 1177 qtips.filter('.' + CLASS_FOCUS).qtip('blur', event); 1178 } 1179 1180 // Set the new z-index 1181 tooltip.addClass(CLASS_FOCUS)[0].style.zIndex = newIndex; 1182 } 1183 } 1184 1185 return this; 1186}; 1187 1188PROTOTYPE.blur = function(event) { 1189 if(!this.rendered || this.destroyed) { return this; } 1190 1191 // Set focused status to FALSE 1192 this.tooltip.removeClass(CLASS_FOCUS); 1193 1194 // tooltipblur event 1195 this._trigger('blur', [ this.tooltip.css('zIndex') ], event); 1196 1197 return this; 1198}; 1199;PROTOTYPE.disable = function(state) { 1200 if(this.destroyed) { return this; } 1201 1202 // If 'toggle' is passed, toggle the current state 1203 if(state === 'toggle') { 1204 state = !(this.rendered ? this.tooltip.hasClass(CLASS_DISABLED) : this.disabled); 1205 } 1206 1207 // Disable if no state passed 1208 else if('boolean' !== typeof state) { 1209 state = TRUE; 1210 } 1211 1212 if(this.rendered) { 1213 this.tooltip.toggleClass(CLASS_DISABLED, state) 1214 .attr('aria-disabled', state); 1215 } 1216 1217 this.disabled = !!state; 1218 1219 return this; 1220}; 1221 1222PROTOTYPE.enable = function() { return this.disable(FALSE); }; 1223;PROTOTYPE._createButton = function() 1224{ 1225 var self = this, 1226 elements = this.elements, 1227 tooltip = elements.tooltip, 1228 button = this.options.content.button, 1229 isString = typeof button === 'string', 1230 close = isString ? button : 'Close tooltip'; 1231 1232 if(elements.button) { elements.button.remove(); } 1233 1234 // Use custom button if one was supplied by user, else use default 1235 if(button.jquery) { 1236 elements.button = button; 1237 } 1238 else { 1239 elements.button = $('<a />', { 1240 'class': 'qtip-close ' + (this.options.style.widget ? '' : NAMESPACE+'-icon'), 1241 'title': close, 1242 'aria-label': close 1243 }) 1244 .prepend( 1245 $('<span />', { 1246 'class': 'ui-icon ui-icon-close', 1247 'html': '×' 1248 }) 1249 ); 1250 } 1251 1252 // Create button and setup attributes 1253 elements.button.appendTo(elements.titlebar || tooltip) 1254 .attr('role', 'button') 1255 .click(function(event) { 1256 if(!tooltip.hasClass(CLASS_DISABLED)) { self.hide(event); } 1257 return FALSE; 1258 }); 1259}; 1260 1261PROTOTYPE._updateButton = function(button) 1262{ 1263 // Make sure tooltip is rendered and if not, return 1264 if(!this.rendered) { return FALSE; } 1265 1266 var elem = this.elements.button; 1267 if(button) { this._createButton(); } 1268 else { elem.remove(); } 1269}; 1270;// Widget class creator 1271function createWidgetClass(cls) { 1272 return WIDGET.concat('').join(cls ? '-'+cls+' ' : ' '); 1273} 1274 1275// Widget class setter method 1276PROTOTYPE._setWidget = function() 1277{ 1278 var on = this.options.style.widget, 1279 elements = this.elements, 1280 tooltip = elements.tooltip, 1281 disabled = tooltip.hasClass(CLASS_DISABLED); 1282 1283 tooltip.removeClass(CLASS_DISABLED); 1284 CLASS_DISABLED = on ? 'ui-state-disabled' : 'qtip-disabled'; 1285 tooltip.toggleClass(CLASS_DISABLED, disabled); 1286 1287 tooltip.toggleClass('ui-helper-reset '+createWidgetClass(), on).toggleClass(CLASS_DEFAULT, this.options.style.def && !on); 1288 1289 if(elements.content) { 1290 elements.content.toggleClass( createWidgetClass('content'), on); 1291 } 1292 if(elements.titlebar) { 1293 elements.titlebar.toggleClass( createWidgetClass('header'), on); 1294 } 1295 if(elements.button) { 1296 elements.button.toggleClass(NAMESPACE+'-icon', !on); 1297 } 1298}; 1299;function delay(callback, duration) { 1300 // If tooltip has displayed, start hide timer 1301 if(duration > 0) { 1302 return setTimeout( 1303 $.proxy(callback, this), duration 1304 ); 1305 } 1306 else{ callback.call(this); } 1307} 1308 1309function showMethod(event) { 1310 if(this.tooltip.hasClass(CLASS_DISABLED)) { return; } 1311 1312 // Clear hide timers 1313 clearTimeout(this.timers.show); 1314 clearTimeout(this.timers.hide); 1315 1316 // Start show timer 1317 this.timers.show = delay.call(this, 1318 function() { this.toggle(TRUE, event); }, 1319 this.options.show.delay 1320 ); 1321} 1322 1323function hideMethod(event) { 1324 if(this.tooltip.hasClass(CLASS_DISABLED) || this.destroyed) { return; } 1325 1326 // Check if new target was actually the tooltip element 1327 var relatedTarget = $(event.relatedTarget), 1328 ontoTooltip = relatedTarget.closest(SELECTOR)[0] === this.tooltip[0], 1329 ontoTarget = relatedTarget[0] === this.options.show.target[0]; 1330 1331 // Clear timers and stop animation queue 1332 clearTimeout(this.timers.show); 1333 clearTimeout(this.timers.hide); 1334 1335 // Prevent hiding if tooltip is fixed and event target is the tooltip. 1336 // Or if mouse positioning is enabled and cursor momentarily overlaps 1337 if(this !== relatedTarget[0] && 1338 (this.options.position.target === 'mouse' && ontoTooltip) || 1339 this.options.hide.fixed && ( 1340 (/mouse(out|leave|move)/).test(event.type) && (ontoTooltip || ontoTarget)) 1341 ) 1342 { 1343 /* eslint-disable no-empty */ 1344 try { 1345 event.preventDefault(); 1346 event.stopImmediatePropagation(); 1347 } catch(e) {} 1348 /* eslint-enable no-empty */ 1349 1350 return; 1351 } 1352 1353 // If tooltip has displayed, start hide timer 1354 this.timers.hide = delay.call(this, 1355 function() { this.toggle(FALSE, event); }, 1356 this.options.hide.delay, 1357 this 1358 ); 1359} 1360 1361function inactiveMethod(event) { 1362 if(this.tooltip.hasClass(CLASS_DISABLED) || !this.options.hide.inactive) { return; } 1363 1364 // Clear timer 1365 clearTimeout(this.timers.inactive); 1366 1367 this.timers.inactive = delay.call(this, 1368 function(){ this.hide(event); }, 1369 this.options.hide.inactive 1370 ); 1371} 1372 1373function repositionMethod(event) { 1374 if(this.rendered && this.tooltip[0].offsetWidth > 0) { this.reposition(event); } 1375} 1376 1377// Store mouse coordinates 1378PROTOTYPE._storeMouse = function(event) { 1379 (this.mouse = $.event.fix(event)).type = 'mousemove'; 1380 return this; 1381}; 1382 1383// Bind events 1384PROTOTYPE._bind = function(targets, events, method, suffix, context) { 1385 if(!targets || !method || !events.length) { return; } 1386 var ns = '.' + this._id + (suffix ? '-'+suffix : ''); 1387 $(targets).bind( 1388 (events.split ? events : events.join(ns + ' ')) + ns, 1389 $.proxy(method, context || this) 1390 ); 1391 return this; 1392}; 1393PROTOTYPE._unbind = function(targets, suffix) { 1394 targets && $(targets).unbind('.' + this._id + (suffix ? '-'+suffix : '')); 1395 return this; 1396}; 1397 1398// Global delegation helper 1399function delegate(selector, events, method) { 1400 $(document.body).delegate(selector, 1401 (events.split ? events : events.join('.'+NAMESPACE + ' ')) + '.'+NAMESPACE, 1402 function() { 1403 var api = QTIP.api[ $.attr(this, ATTR_ID) ]; 1404 api && !api.disabled && method.apply(api, arguments); 1405 } 1406 ); 1407} 1408// Event trigger 1409PROTOTYPE._trigger = function(type, args, event) { 1410 var callback = new $.Event('tooltip'+type); 1411 callback.originalEvent = event && $.extend({}, event) || this.cache.event || NULL; 1412 1413 this.triggering = type; 1414 this.tooltip.trigger(callback, [this].concat(args || [])); 1415 this.triggering = FALSE; 1416 1417 return !callback.isDefaultPrevented(); 1418}; 1419 1420PROTOTYPE._bindEvents = function(showEvents, hideEvents, showTargets, hideTargets, showCallback, hideCallback) { 1421 // Get tasrgets that lye within both 1422 var similarTargets = showTargets.filter( hideTargets ).add( hideTargets.filter(showTargets) ), 1423 toggleEvents = []; 1424 1425 // If hide and show targets are the same... 1426 if(similarTargets.length) { 1427 1428 // Filter identical show/hide events 1429 $.each(hideEvents, function(i, type) { 1430 var showIndex = $.inArray(type, showEvents); 1431 1432 // Both events are identical, remove from both hide and show events 1433 // and append to toggleEvents 1434 showIndex > -1 && toggleEvents.push( showEvents.splice( showIndex, 1 )[0] ); 1435 }); 1436 1437 // Toggle events are special case of identical show/hide events, which happen in sequence 1438 if(toggleEvents.length) { 1439 // Bind toggle events to the similar targets 1440 this._bind(similarTargets, toggleEvents, function(event) { 1441 var state = this.rendered ? this.tooltip[0].offsetWidth > 0 : false; 1442 (state ? hideCallback : showCallback).call(this, event); 1443 }); 1444 1445 // Remove the similar targets from the regular show/hide bindings 1446 showTargets = showTargets.not(similarTargets); 1447 hideTargets = hideTargets.not(similarTargets); 1448 } 1449 } 1450 1451 // Apply show/hide/toggle events 1452 this._bind(showTargets, showEvents, showCallback); 1453 this._bind(hideTargets, hideEvents, hideCallback); 1454}; 1455 1456PROTOTYPE._assignInitialEvents = function(event) { 1457 var options = this.options, 1458 showTarget = options.show.target, 1459 hideTarget = options.hide.target, 1460 showEvents = options.show.event ? $.trim('' + options.show.event).split(' ') : [], 1461 hideEvents = options.hide.event ? $.trim('' + options.hide.event).split(' ') : []; 1462 1463 // Catch remove/removeqtip events on target element to destroy redundant tooltips 1464 this._bind(this.elements.target, ['remove', 'removeqtip'], function() { 1465 this.destroy(true); 1466 }, 'destroy'); 1467 1468 /* 1469 * Make sure hoverIntent functions properly by using mouseleave as a hide event if 1470 * mouseenter/mouseout is used for show.event, even if it isn't in the users options. 1471 */ 1472 if(/mouse(over|enter)/i.test(options.show.event) && !/mouse(out|leave)/i.test(options.hide.event)) { 1473 hideEvents.push('mouseleave'); 1474 } 1475 1476 /* 1477 * Also make sure initial mouse targetting works correctly by caching mousemove coords 1478 * on show targets before the tooltip has rendered. Also set onTarget when triggered to 1479 * keep mouse tracking working. 1480 */ 1481 this._bind(showTarget, 'mousemove', function(moveEvent) { 1482 this._storeMouse(moveEvent); 1483 this.cache.onTarget = TRUE; 1484 }); 1485 1486 // Define hoverIntent function 1487 function hoverIntent(hoverEvent) { 1488 // Only continue if tooltip isn't disabled 1489 if(this.disabled || this.destroyed) { return FALSE; } 1490 1491 // Cache the event data 1492 this.cache.event = hoverEvent && $.event.fix(hoverEvent); 1493 this.cache.target = hoverEvent && $(hoverEvent.target); 1494 1495 // Start the event sequence 1496 clearTimeout(this.timers.show); 1497 this.timers.show = delay.call(this, 1498 function() { this.render(typeof hoverEvent === 'object' || options.show.ready); }, 1499 options.prerender ? 0 : options.show.delay 1500 ); 1501 } 1502 1503 // Filter and bind events 1504 this._bindEvents(showEvents, hideEvents, showTarget, hideTarget, hoverIntent, function() { 1505 if(!this.timers) { return FALSE; } 1506 clearTimeout(this.timers.show); 1507 }); 1508 1509 // Prerendering is enabled, create tooltip now 1510 if(options.show.ready || options.prerender) { hoverIntent.call(this, event); } 1511}; 1512 1513// Event assignment method 1514PROTOTYPE._assignEvents = function() { 1515 var self = this, 1516 options = this.options, 1517 posOptions = options.position, 1518 1519 tooltip = this.tooltip, 1520 showTarget = options.show.target, 1521 hideTarget = options.hide.target, 1522 containerTarget = posOptions.container, 1523 viewportTarget = posOptions.viewport, 1524 documentTarget = $(document), 1525 windowTarget = $(window), 1526 1527 showEvents = options.show.event ? $.trim('' + options.show.event).split(' ') : [], 1528 hideEvents = options.hide.event ? $.trim('' + options.hide.event).split(' ') : []; 1529 1530 1531 // Assign passed event callbacks 1532 $.each(options.events, function(name, callback) { 1533 self._bind(tooltip, name === 'toggle' ? ['tooltipshow','tooltiphide'] : ['tooltip'+name], callback, null, tooltip); 1534 }); 1535 1536 // Hide tooltips when leaving current window/frame (but not select/option elements) 1537 if(/mouse(out|leave)/i.test(options.hide.event) && options.hide.leave === 'window') { 1538 this._bind(documentTarget, ['mouseout', 'blur'], function(event) { 1539 if(!/select|option/.test(event.target.nodeName) && !event.relatedTarget) { 1540 this.hide(event); 1541 } 1542 }); 1543 } 1544 1545 // Enable hide.fixed by adding appropriate class 1546 if(options.hide.fixed) { 1547 hideTarget = hideTarget.add( tooltip.addClass(CLASS_FIXED) ); 1548 } 1549 1550 /* 1551 * Make sure hoverIntent functions properly by using mouseleave to clear show timer if 1552 * mouseenter/mouseout is used for show.event, even if it isn't in the users options. 1553 */ 1554 else if(/mouse(over|enter)/i.test(options.show.event)) { 1555 this._bind(hideTarget, 'mouseleave', function() { 1556 clearTimeout(this.timers.show); 1557 }); 1558 } 1559 1560 // Hide tooltip on document mousedown if unfocus events are enabled 1561 if(('' + options.hide.event).indexOf('unfocus') > -1) { 1562 this._bind(containerTarget.closest('html'), ['mousedown', 'touchstart'], function(event) { 1563 var elem = $(event.target), 1564 enabled = this.rendered && !this.tooltip.hasClass(CLASS_DISABLED) && this.tooltip[0].offsetWidth > 0, 1565 isAncestor = elem.parents(SELECTOR).filter(this.tooltip[0]).length > 0; 1566 1567 if(elem[0] !== this.target[0] && elem[0] !== this.tooltip[0] && !isAncestor && 1568 !this.target.has(elem[0]).length && enabled 1569 ) { 1570 this.hide(event); 1571 } 1572 }); 1573 } 1574 1575 // Check if the tooltip hides when inactive 1576 if('number' === typeof options.hide.inactive) { 1577 // Bind inactive method to show target(s) as a custom event 1578 this._bind(showTarget, 'qtip-'+this.id+'-inactive', inactiveMethod, 'inactive'); 1579 1580 // Define events which reset the 'inactive' event handler 1581 this._bind(hideTarget.add(tooltip), QTIP.inactiveEvents, inactiveMethod); 1582 } 1583 1584 // Filter and bind events 1585 this._bindEvents(showEvents, hideEvents, showTarget, hideTarget, showMethod, hideMethod); 1586 1587 // Mouse movement bindings 1588 this._bind(showTarget.add(tooltip), 'mousemove', function(event) { 1589 // Check if the tooltip hides when mouse is moved a certain distance 1590 if('number' === typeof options.hide.distance) { 1591 var origin = this.cache.origin || {}, 1592 limit = this.options.hide.distance, 1593 abs = Math.abs; 1594 1595 // Check if the movement has gone beyond the limit, and hide it if so 1596 if(abs(event.pageX - origin.pageX) >= limit || abs(event.pageY - origin.pageY) >= limit) { 1597 this.hide(event); 1598 } 1599 } 1600 1601 // Cache mousemove coords on show targets 1602 this._storeMouse(event); 1603 }); 1604 1605 // Mouse positioning events 1606 if(posOptions.target === 'mouse') { 1607 // If mouse adjustment is on... 1608 if(posOptions.adjust.mouse) { 1609 // Apply a mouseleave event so we don't get problems with overlapping 1610 if(options.hide.event) { 1611 // Track if we're on the target or not 1612 this._bind(showTarget, ['mouseenter', 'mouseleave'], function(event) { 1613 if(!this.cache) {return FALSE; } 1614 this.cache.onTarget = event.type === 'mouseenter'; 1615 }); 1616 } 1617 1618 // Update tooltip position on mousemove 1619 this._bind(documentTarget, 'mousemove', function(event) { 1620 // Update the tooltip position only if the tooltip is visible and adjustment is enabled 1621 if(this.rendered && this.cache.onTarget && !this.tooltip.hasClass(CLASS_DISABLED) && this.tooltip[0].offsetWidth > 0) { 1622 this.reposition(event); 1623 } 1624 }); 1625 } 1626 } 1627 1628 // Adjust positions of the tooltip on window resize if enabled 1629 if(posOptions.adjust.resize || viewportTarget.length) { 1630 this._bind( $.event.special.resize ? viewportTarget : windowTarget, 'resize', repositionMethod ); 1631 } 1632 1633 // Adjust tooltip position on scroll of the window or viewport element if present 1634 if(posOptions.adjust.scroll) { 1635 this._bind( windowTarget.add(posOptions.container), 'scroll', repositionMethod ); 1636 } 1637}; 1638 1639// Un-assignment method 1640PROTOTYPE._unassignEvents = function() { 1641 var options = this.options, 1642 showTargets = options.show.target, 1643 hideTargets = options.hide.target, 1644 targets = $.grep([ 1645 this.elements.target[0], 1646 this.rendered && this.tooltip[0], 1647 options.position.container[0], 1648 options.position.viewport[0], 1649 options.position.container.closest('html')[0], // unfocus 1650 window, 1651 document 1652 ], function(i) { 1653 return typeof i === 'object'; 1654 }); 1655 1656 // Add show and hide targets if they're valid 1657 if(showTargets && showTargets.toArray) { 1658 targets = targets.concat(showTargets.toArray()); 1659 } 1660 if(hideTargets && hideTargets.toArray) { 1661 targets = targets.concat(hideTargets.toArray()); 1662 } 1663 1664 // Unbind the events 1665 this._unbind(targets) 1666 ._unbind(targets, 'destroy') 1667 ._unbind(targets, 'inactive'); 1668}; 1669 1670// Apply common event handlers using delegate (avoids excessive .bind calls!) 1671$(function() { 1672 delegate(SELECTOR, ['mouseenter', 'mouseleave'], function(event) { 1673 var state = event.type === 'mouseenter', 1674 tooltip = $(event.currentTarget), 1675 target = $(event.relatedTarget || event.target), 1676 options = this.options; 1677 1678 // On mouseenter... 1679 if(state) { 1680 // Focus the tooltip on mouseenter (z-index stacking) 1681 this.focus(event); 1682 1683 // Clear hide timer on tooltip hover to prevent it from closing 1684 tooltip.hasClass(CLASS_FIXED) && !tooltip.hasClass(CLASS_DISABLED) && clearTimeout(this.timers.hide); 1685 } 1686 1687 // On mouseleave... 1688 else { 1689 // When mouse tracking is enabled, hide when we leave the tooltip and not onto the show target (if a hide event is set) 1690 if(options.position.target === 'mouse' && options.position.adjust.mouse && 1691 options.hide.event && options.show.target && !target.closest(options.show.target[0]).length) { 1692 this.hide(event); 1693 } 1694 } 1695 1696 // Add hover class 1697 tooltip.toggleClass(CLASS_HOVER, state); 1698 }); 1699 1700 // Define events which reset the 'inactive' event handler 1701 delegate('['+ATTR_ID+']', INACTIVE_EVENTS, inactiveMethod); 1702}); 1703;// Initialization method 1704function init(elem, id, opts) { 1705 var obj, posOptions, attr, config, title, 1706 1707 // Setup element references 1708 docBody = $(document.body), 1709 1710 // Use document body instead of document element if needed 1711 newTarget = elem[0] === document ? docBody : elem, 1712 1713 // Grab metadata from element if plugin is present 1714 metadata = elem.metadata ? elem.metadata(opts.metadata) : NULL, 1715 1716 // If metadata type if HTML5, grab 'name' from the object instead, or use the regular data object otherwise 1717 metadata5 = opts.metadata.type === 'html5' && metadata ? metadata[opts.metadata.name] : NULL, 1718 1719 // Grab data from metadata.name (or data-qtipopts as fallback) using .data() method, 1720 html5 = elem.data(opts.metadata.name || 'qtipopts'); 1721 1722 // If we don't get an object returned attempt to parse it manualyl without parseJSON 1723 /* eslint-disable no-empty */ 1724 try { html5 = typeof html5 === 'string' ? $.parseJSON(html5) : html5; } 1725 catch(e) {} 1726 /* eslint-enable no-empty */ 1727 1728 // Merge in and sanitize metadata 1729 config = $.extend(TRUE, {}, QTIP.defaults, opts, 1730 typeof html5 === 'object' ? sanitizeOptions(html5) : NULL, 1731 sanitizeOptions(metadata5 || metadata)); 1732 1733 // Re-grab our positioning options now we've merged our metadata and set id to passed value 1734 posOptions = config.position; 1735 config.id = id; 1736 1737 // Setup missing content if none is detected 1738 if('boolean' === typeof config.content.text) { 1739 attr = elem.attr(config.content.attr); 1740 1741 // Grab from supplied attribute if available 1742 if(config.content.attr !== FALSE && attr) { config.content.text = attr; } 1743 1744 // No valid content was found, abort render 1745 else { return FALSE; } 1746 } 1747 1748 // Setup target options 1749 if(!posOptions.container.length) { posOptions.container = docBody; } 1750 if(posOptions.target === FALSE) { posOptions.target = newTarget; } 1751 if(config.show.target === FALSE) { config.show.target = newTarget; } 1752 if(config.show.solo === TRUE) { config.show.solo = posOptions.container.closest('body'); } 1753 if(config.hide.target === FALSE) { config.hide.target = newTarget; } 1754 if(config.position.viewport === TRUE) { config.position.viewport = posOptions.container; } 1755 1756 // Ensure we only use a single container 1757 posOptions.container = posOptions.container.eq(0); 1758 1759 // Convert position corner values into x and y strings 1760 posOptions.at = new CORNER(posOptions.at, TRUE); 1761 posOptions.my = new CORNER(posOptions.my); 1762 1763 // Destroy previous tooltip if overwrite is enabled, or skip element if not 1764 if(elem.data(NAMESPACE)) { 1765 if(config.overwrite) { 1766 elem.qtip('destroy', true); 1767 } 1768 else if(config.overwrite === FALSE) { 1769 return FALSE; 1770 } 1771 } 1772 1773 // Add has-qtip attribute 1774 elem.attr(ATTR_HAS, id); 1775 1776 // Remove title attribute and store it if present 1777 if(config.suppress && (title = elem.attr('title'))) { 1778 // Final attr call fixes event delegatiom and IE default tooltip showing problem 1779 elem.removeAttr('title').attr(oldtitle, title).attr('title', ''); 1780 } 1781 1782 // Initialize the tooltip and add API reference 1783 obj = new QTip(elem, config, id, !!attr); 1784 elem.data(NAMESPACE, obj); 1785 1786 return obj; 1787} 1788 1789// jQuery $.fn extension method 1790QTIP = $.fn.qtip = function(options, notation, newValue) 1791{ 1792 var command = ('' + options).toLowerCase(), // Parse command 1793 returned = NULL, 1794 args = $.makeArray(arguments).slice(1), 1795 event = args[args.length - 1], 1796 opts = this[0] ? $.data(this[0], NAMESPACE) : NULL; 1797 1798 // Check for API request 1799 if(!arguments.length && opts || command === 'api') { 1800 return opts; 1801 } 1802 1803 // Execute API command if present 1804 else if('string' === typeof options) { 1805 this.each(function() { 1806 var api = $.data(this, NAMESPACE); 1807 if(!api) { return TRUE; } 1808 1809 // Cache the event if possible 1810 if(event && event.timeStamp) { api.cache.event = event; } 1811 1812 // Check for specific API commands 1813 if(notation && (command === 'option' || command === 'options')) { 1814 if(newValue !== undefined || $.isPlainObject(notation)) { 1815 api.set(notation, newValue); 1816 } 1817 else { 1818 returned = api.get(notation); 1819 return FALSE; 1820 } 1821 } 1822 1823 // Execute API command 1824 else if(api[command]) { 1825 api[command].apply(api, args); 1826 } 1827 }); 1828 1829 return returned !== NULL ? returned : this; 1830 } 1831 1832 // No API commands. validate provided options and setup qTips 1833 else if('object' === typeof options || !arguments.length) { 1834 // Sanitize options first 1835 opts = sanitizeOptions($.extend(TRUE, {}, options)); 1836 1837 return this.each(function(i) { 1838 var api, id; 1839 1840 // Find next available ID, or use custom ID if provided 1841 id = $.isArray(opts.id) ? opts.id[i] : opts.id; 1842 id = !id || id === FALSE || id.length < 1 || QTIP.api[id] ? QTIP.nextid++ : id; 1843 1844 // Initialize the qTip and re-grab newly sanitized options 1845 api = init($(this), id, opts); 1846 if(api === FALSE) { return TRUE; } 1847 else { QTIP.api[id] = api; } 1848 1849 // Initialize plugins 1850 $.each(PLUGINS, function() { 1851 if(this.initialize === 'initialize') { this(api); } 1852 }); 1853 1854 // Assign initial pre-render events 1855 api._assignInitialEvents(event); 1856 }); 1857 } 1858}; 1859 1860// Expose class 1861$.qtip = QTip; 1862 1863// Populated in render method 1864QTIP.api = {}; 1865;$.each({ 1866 /* Allow other plugins to successfully retrieve the title of an element with a qTip applied */ 1867 attr: function(attr, val) { 1868 if(this.length) { 1869 var self = this[0], 1870 title = 'title', 1871 api = $.data(self, 'qtip'); 1872 1873 if(attr === title && api && api.options && 'object' === typeof api && 'object' === typeof api.options && api.options.suppress) { 1874 if(arguments.length < 2) { 1875 return $.attr(self, oldtitle); 1876 } 1877 1878 // If qTip is rendered and title was originally used as content, update it 1879 if(api && api.options.content.attr === title && api.cache.attr) { 1880 api.set('content.text', val); 1881 } 1882 1883 // Use the regular attr method to set, then cache the result 1884 return this.attr(oldtitle, val); 1885 } 1886 } 1887 1888 return $.fn['attr'+replaceSuffix].apply(this, arguments); 1889 }, 1890 1891 /* Allow clone to correctly retrieve cached title attributes */ 1892 clone: function(keepData) { 1893 // Clone our element using the real clone method 1894 var elems = $.fn['clone'+replaceSuffix].apply(this, arguments); 1895 1896 // Grab all elements with an oldtitle set, and change it to regular title attribute, if keepData is false 1897 if(!keepData) { 1898 elems.filter('['+oldtitle+']').attr('title', function() { 1899 return $.attr(this, oldtitle); 1900 }) 1901 .removeAttr(oldtitle); 1902 } 1903 1904 return elems; 1905 } 1906}, function(name, func) { 1907 if(!func || $.fn[name+replaceSuffix]) { return TRUE; } 1908 1909 var old = $.fn[name+replaceSuffix] = $.fn[name]; 1910 $.fn[name] = function() { 1911 return func.apply(this, arguments) || old.apply(this, arguments); 1912 }; 1913}); 1914 1915/* Fire off 'removeqtip' handler in $.cleanData if jQuery UI not present (it already does similar). 1916 * This snippet is taken directly from jQuery UI source code found here: 1917 * http://code.jquery.com/ui/jquery-ui-git.js 1918 */ 1919if(!$.ui) { 1920 $['cleanData'+replaceSuffix] = $.cleanData; 1921 $.cleanData = function( elems ) { 1922 for(var i = 0, elem; (elem = $( elems[i] )).length; i++) { 1923 if(elem.attr(ATTR_HAS)) { 1924 /* eslint-disable no-empty */ 1925 try { elem.triggerHandler('removeqtip'); } 1926 catch( e ) {} 1927 /* eslint-enable no-empty */ 1928 } 1929 } 1930 $['cleanData'+replaceSuffix].apply(this, arguments); 1931 }; 1932} 1933;// qTip version 1934QTIP.version = '3.0.3'; 1935 1936// Base ID for all qTips 1937QTIP.nextid = 0; 1938 1939// Inactive events array 1940QTIP.inactiveEvents = INACTIVE_EVENTS; 1941 1942// Base z-index for all qTips 1943QTIP.zindex = 15000; 1944 1945// Define configuration defaults 1946QTIP.defaults = { 1947 prerender: FALSE, 1948 id: FALSE, 1949 overwrite: TRUE, 1950 suppress: TRUE, 1951 content: { 1952 text: TRUE, 1953 attr: 'title', 1954 title: FALSE, 1955 button: FALSE 1956 }, 1957 position: { 1958 my: 'top left', 1959 at: 'bottom right', 1960 target: FALSE, 1961 container: FALSE, 1962 viewport: FALSE, 1963 adjust: { 1964 x: 0, y: 0, 1965 mouse: TRUE, 1966 scroll: TRUE, 1967 resize: TRUE, 1968 method: 'flipinvert flipinvert' 1969 }, 1970 effect: function(api, pos) { 1971 $(this).animate(pos, { 1972 duration: 200, 1973 queue: FALSE 1974 }); 1975 } 1976 }, 1977 show: { 1978 target: FALSE, 1979 event: 'mouseenter', 1980 effect: TRUE, 1981 delay: 90, 1982 solo: FALSE, 1983 ready: FALSE, 1984 autofocus: FALSE 1985 }, 1986 hide: { 1987 target: FALSE, 1988 event: 'mouseleave', 1989 effect: TRUE, 1990 delay: 0, 1991 fixed: FALSE, 1992 inactive: FALSE, 1993 leave: 'window', 1994 distance: FALSE 1995 }, 1996 style: { 1997 classes: '', 1998 widget: FALSE, 1999 width: FALSE, 2000 height: FALSE, 2001 def: TRUE 2002 }, 2003 events: { 2004 render: NULL, 2005 move: NULL, 2006 show: NULL, 2007 hide: NULL, 2008 toggle: NULL, 2009 visible: NULL, 2010 hidden: NULL, 2011 focus: NULL, 2012 blur: NULL 2013 } 2014}; 2015;var TIP, 2016createVML, 2017SCALE, 2018PIXEL_RATIO, 2019BACKING_STORE_RATIO, 2020 2021// Common CSS strings 2022MARGIN = 'margin', 2023BORDER = 'border', 2024COLOR = 'color', 2025BG_COLOR = 'background-color', 2026TRANSPARENT = 'transparent', 2027IMPORTANT = ' !important', 2028 2029// Check if the browser supports <canvas/> elements 2030HASCANVAS = !!document.createElement('canvas').getContext, 2031 2032// Invalid colour values used in parseColours() 2033INVALID = /rgba?\(0, 0, 0(, 0)?\)|transparent|#123456/i; 2034 2035// Camel-case method, taken from jQuery source 2036// http://code.jquery.com/jquery-1.8.0.js 2037function camel(s) { return s.charAt(0).toUpperCase() + s.slice(1); } 2038 2039/* 2040 * Modified from Modernizr's testPropsAll() 2041 * http://modernizr.com/downloads/modernizr-latest.js 2042 */ 2043var cssProps = {}, cssPrefixes = ['Webkit', 'O', 'Moz', 'ms']; 2044function vendorCss(elem, prop) { 2045 var ucProp = prop.charAt(0).toUpperCase() + prop.slice(1), 2046 props = (prop + ' ' + cssPrefixes.join(ucProp + ' ') + ucProp).split(' '), 2047 cur, val, i = 0; 2048 2049 // If the property has already been mapped... 2050 if(cssProps[prop]) { return elem.css(cssProps[prop]); } 2051 2052 while(cur = props[i++]) { 2053 if((val = elem.css(cur)) !== undefined) { 2054 cssProps[prop] = cur; 2055 return val; 2056 } 2057 } 2058} 2059 2060// Parse a given elements CSS property into an int 2061function intCss(elem, prop) { 2062 return Math.ceil(parseFloat(vendorCss(elem, prop))); 2063} 2064 2065 2066// VML creation (for IE only) 2067if(!HASCANVAS) { 2068 createVML = function(tag, props, style) { 2069 return '<qtipvml:'+tag+' xmlns="urn:schemas-microsoft.com:vml" class="qtip-vml" '+(props||'')+ 2070 ' style="behavior: url(#default#VML); '+(style||'')+ '" />'; 2071 }; 2072} 2073 2074// Canvas only definitions 2075else { 2076 PIXEL_RATIO = window.devicePixelRatio || 1; 2077 BACKING_STORE_RATIO = (function() { 2078 var context = document.createElement('canvas').getContext('2d'); 2079 return context.backingStorePixelRatio || context.webkitBackingStorePixelRatio || context.mozBackingStorePixelRatio || 2080 context.msBackingStorePixelRatio || context.oBackingStorePixelRatio || 1; 2081 })(); 2082 SCALE = PIXEL_RATIO / BACKING_STORE_RATIO; 2083} 2084 2085 2086function Tip(qtip, options) { 2087 this._ns = 'tip'; 2088 this.options = options; 2089 this.offset = options.offset; 2090 this.size = [ options.width, options.height ]; 2091 2092 // Initialize 2093 this.qtip = qtip; 2094 this.init(qtip); 2095} 2096 2097$.extend(Tip.prototype, { 2098 init: function(qtip) { 2099 var context, tip; 2100 2101 // Create tip element and prepend to the tooltip 2102 tip = this.element = qtip.elements.tip = $('<div />', { 'class': NAMESPACE+'-tip' }).prependTo(qtip.tooltip); 2103 2104 // Create tip drawing element(s) 2105 if(HASCANVAS) { 2106 // save() as soon as we create the canvas element so FF2 doesn't bork on our first restore()! 2107 context = $('<canvas />').appendTo(this.element)[0].getContext('2d'); 2108 2109 // Setup constant parameters 2110 context.lineJoin = 'miter'; 2111 context.miterLimit = 100000; 2112 context.save(); 2113 } 2114 else { 2115 context = createVML('shape', 'coordorigin="0,0"', 'position:absolute;'); 2116 this.element.html(context + context); 2117 2118 // Prevent mousing down on the tip since it causes problems with .live() handling in IE due to VML 2119 qtip._bind( $('*', tip).add(tip), ['click', 'mousedown'], function(event) { event.stopPropagation(); }, this._ns); 2120 } 2121 2122 // Bind update events 2123 qtip._bind(qtip.tooltip, 'tooltipmove', this.reposition, this._ns, this); 2124 2125 // Create it 2126 this.create(); 2127 }, 2128 2129 _swapDimensions: function() { 2130 this.size[0] = this.options.height; 2131 this.size[1] = this.options.width; 2132 }, 2133 _resetDimensions: function() { 2134 this.size[0] = this.options.width; 2135 this.size[1] = this.options.height; 2136 }, 2137 2138 _useTitle: function(corner) { 2139 var titlebar = this.qtip.elements.titlebar; 2140 return titlebar && ( 2141 corner.y === TOP || corner.y === CENTER && this.element.position().top + this.size[1] / 2 + this.options.offset < titlebar.outerHeight(TRUE) 2142 ); 2143 }, 2144 2145 _parseCorner: function(corner) { 2146 var my = this.qtip.options.position.my; 2147 2148 // Detect corner and mimic properties 2149 if(corner === FALSE || my === FALSE) { 2150 corner = FALSE; 2151 } 2152 else if(corner === TRUE) { 2153 corner = new CORNER( my.string() ); 2154 } 2155 else if(!corner.string) { 2156 corner = new CORNER(corner); 2157 corner.fixed = TRUE; 2158 } 2159 2160 return corner; 2161 }, 2162 2163 _parseWidth: function(corner, side, use) { 2164 var elements = this.qtip.elements, 2165 prop = BORDER + camel(side) + 'Width'; 2166 2167 return (use ? intCss(use, prop) : 2168 intCss(elements.content, prop) || 2169 intCss(this._useTitle(corner) && elements.titlebar || elements.content, prop) || 2170 intCss(elements.tooltip, prop) 2171 ) || 0; 2172 }, 2173 2174 _parseRadius: function(corner) { 2175 var elements = this.qtip.elements, 2176 prop = BORDER + camel(corner.y) + camel(corner.x) + 'Radius'; 2177 2178 return BROWSER.ie < 9 ? 0 : 2179 intCss(this._useTitle(corner) && elements.titlebar || elements.content, prop) || 2180 intCss(elements.tooltip, prop) || 0; 2181 }, 2182 2183 _invalidColour: function(elem, prop, compare) { 2184 var val = elem.css(prop); 2185 return !val || compare && val === elem.css(compare) || INVALID.test(val) ? FALSE : val; 2186 }, 2187 2188 _parseColours: function(corner) { 2189 var elements = this.qtip.elements, 2190 tip = this.element.css('cssText', ''), 2191 borderSide = BORDER + camel(corner[ corner.precedance ]) + camel(COLOR), 2192 colorElem = this._useTitle(corner) && elements.titlebar || elements.content, 2193 css = this._invalidColour, color = []; 2194 2195 // Attempt to detect the background colour from various elements, left-to-right precedance 2196 color[0] = css(tip, BG_COLOR) || css(colorElem, BG_COLOR) || css(elements.content, BG_COLOR) || 2197 css(elements.tooltip, BG_COLOR) || tip.css(BG_COLOR); 2198 2199 // Attempt to detect the correct border side colour from various elements, left-to-right precedance 2200 color[1] = css(tip, borderSide, COLOR) || css(colorElem, borderSide, COLOR) || 2201 css(elements.content, borderSide, COLOR) || css(elements.tooltip, borderSide, COLOR) || elements.tooltip.css(borderSide); 2202 2203 // Reset background and border colours 2204 $('*', tip).add(tip).css('cssText', BG_COLOR+':'+TRANSPARENT+IMPORTANT+';'+BORDER+':0'+IMPORTANT+';'); 2205 2206 return color; 2207 }, 2208 2209 _calculateSize: function(corner) { 2210 var y = corner.precedance === Y, 2211 width = this.options.width, 2212 height = this.options.height, 2213 isCenter = corner.abbrev() === 'c', 2214 base = (y ? width: height) * (isCenter ? 0.5 : 1), 2215 pow = Math.pow, 2216 round = Math.round, 2217 bigHyp, ratio, result, 2218 2219 smallHyp = Math.sqrt( pow(base, 2) + pow(height, 2) ), 2220 hyp = [ 2221 this.border / base * smallHyp, 2222 this.border / height * smallHyp 2223 ]; 2224 2225 hyp[2] = Math.sqrt( pow(hyp[0], 2) - pow(this.border, 2) ); 2226 hyp[3] = Math.sqrt( pow(hyp[1], 2) - pow(this.border, 2) ); 2227 2228 bigHyp = smallHyp + hyp[2] + hyp[3] + (isCenter ? 0 : hyp[0]); 2229 ratio = bigHyp / smallHyp; 2230 2231 result = [ round(ratio * width), round(ratio * height) ]; 2232 return y ? result : result.reverse(); 2233 }, 2234 2235 // Tip coordinates calculator 2236 _calculateTip: function(corner, size, scale) { 2237 scale = scale || 1; 2238 size = size || this.size; 2239 2240 var width = size[0] * scale, 2241 height = size[1] * scale, 2242 width2 = Math.ceil(width / 2), height2 = Math.ceil(height / 2), 2243 2244 // Define tip coordinates in terms of height and width values 2245 tips = { 2246 br: [0,0, width,height, width,0], 2247 bl: [0,0, width,0, 0,height], 2248 tr: [0,height, width,0, width,height], 2249 tl: [0,0, 0,height, width,height], 2250 tc: [0,height, width2,0, width,height], 2251 bc: [0,0, width,0, width2,height], 2252 rc: [0,0, width,height2, 0,height], 2253 lc: [width,0, width,height, 0,height2] 2254 }; 2255 2256 // Set common side shapes 2257 tips.lt = tips.br; tips.rt = tips.bl; 2258 tips.lb = tips.tr; tips.rb = tips.tl; 2259 2260 return tips[ corner.abbrev() ]; 2261 }, 2262 2263 // Tip coordinates drawer (canvas) 2264 _drawCoords: function(context, coords) { 2265 context.beginPath(); 2266 context.moveTo(coords[0], coords[1]); 2267 context.lineTo(coords[2], coords[3]); 2268 context.lineTo(coords[4], coords[5]); 2269 context.closePath(); 2270 }, 2271 2272 create: function() { 2273 // Determine tip corner 2274 var c = this.corner = (HASCANVAS || BROWSER.ie) && this._parseCorner(this.options.corner); 2275 2276 // If we have a tip corner... 2277 this.enabled = !!this.corner && this.corner.abbrev() !== 'c'; 2278 if(this.enabled) { 2279 // Cache it 2280 this.qtip.cache.corner = c.clone(); 2281 2282 // Create it 2283 this.update(); 2284 } 2285 2286 // Toggle tip element 2287 this.element.toggle(this.enabled); 2288 2289 return this.corner; 2290 }, 2291 2292 update: function(corner, position) { 2293 if(!this.enabled) { return this; } 2294 2295 var elements = this.qtip.elements, 2296 tip = this.element, 2297 inner = tip.children(), 2298 options = this.options, 2299 curSize = this.size, 2300 mimic = options.mimic, 2301 round = Math.round, 2302 color, precedance, context, 2303 coords, bigCoords, translate, newSize, border; 2304 2305 // Re-determine tip if not already set 2306 if(!corner) { corner = this.qtip.cache.corner || this.corner; } 2307 2308 // Use corner property if we detect an invalid mimic value 2309 if(mimic === FALSE) { mimic = corner; } 2310 2311 // Otherwise inherit mimic properties from the corner object as necessary 2312 else { 2313 mimic = new CORNER(mimic); 2314 mimic.precedance = corner.precedance; 2315 2316 if(mimic.x === 'inherit') { mimic.x = corner.x; } 2317 else if(mimic.y === 'inherit') { mimic.y = corner.y; } 2318 else if(mimic.x === mimic.y) { 2319 mimic[ corner.precedance ] = corner[ corner.precedance ]; 2320 } 2321 } 2322 precedance = mimic.precedance; 2323 2324 // Ensure the tip width.height are relative to the tip position 2325 if(corner.precedance === X) { this._swapDimensions(); } 2326 else { this._resetDimensions(); } 2327 2328 // Update our colours 2329 color = this.color = this._parseColours(corner); 2330 2331 // Detect border width, taking into account colours 2332 if(color[1] !== TRANSPARENT) { 2333 // Grab border width 2334 border = this.border = this._parseWidth(corner, corner[corner.precedance]); 2335 2336 // If border width isn't zero, use border color as fill if it's not invalid (1.0 style tips) 2337 if(options.border && border < 1 && !INVALID.test(color[1])) { color[0] = color[1]; } 2338 2339 // Set border width (use detected border width if options.border is true) 2340 this.border = border = options.border !== TRUE ? options.border : border; 2341 } 2342 2343 // Border colour was invalid, set border to zero 2344 else { this.border = border = 0; } 2345 2346 // Determine tip size 2347 newSize = this.size = this._calculateSize(corner); 2348 tip.css({ 2349 width: newSize[0], 2350 height: newSize[1], 2351 lineHeight: newSize[1]+'px' 2352 }); 2353 2354 // Calculate tip translation 2355 if(corner.precedance === Y) { 2356 translate = [ 2357 round(mimic.x === LEFT ? border : mimic.x === RIGHT ? newSize[0] - curSize[0] - border : (newSize[0] - curSize[0]) / 2), 2358 round(mimic.y === TOP ? newSize[1] - curSize[1] : 0) 2359 ]; 2360 } 2361 else { 2362 translate = [ 2363 round(mimic.x === LEFT ? newSize[0] - curSize[0] : 0), 2364 round(mimic.y === TOP ? border : mimic.y === BOTTOM ? newSize[1] - curSize[1] - border : (newSize[1] - curSize[1]) / 2) 2365 ]; 2366 } 2367 2368 // Canvas drawing implementation 2369 if(HASCANVAS) { 2370 // Grab canvas context and clear/save it 2371 context = inner[0].getContext('2d'); 2372 context.restore(); context.save(); 2373 context.clearRect(0,0,6000,6000); 2374 2375 // Calculate coordinates 2376 coords = this._calculateTip(mimic, curSize, SCALE); 2377 bigCoords = this._calculateTip(mimic, this.size, SCALE); 2378 2379 // Set the canvas size using calculated size 2380 inner.attr(WIDTH, newSize[0] * SCALE).attr(HEIGHT, newSize[1] * SCALE); 2381 inner.css(WIDTH, newSize[0]).css(HEIGHT, newSize[1]); 2382 2383 // Draw the outer-stroke tip 2384 this._drawCoords(context, bigCoords); 2385 context.fillStyle = color[1]; 2386 context.fill(); 2387 2388 // Draw the actual tip 2389 context.translate(translate[0] * SCALE, translate[1] * SCALE); 2390 this._drawCoords(context, coords); 2391 context.fillStyle = color[0]; 2392 context.fill(); 2393 } 2394 2395 // VML (IE Proprietary implementation) 2396 else { 2397 // Calculate coordinates 2398 coords = this._calculateTip(mimic); 2399 2400 // Setup coordinates string 2401 coords = 'm' + coords[0] + ',' + coords[1] + ' l' + coords[2] + 2402 ',' + coords[3] + ' ' + coords[4] + ',' + coords[5] + ' xe'; 2403 2404 // Setup VML-specific offset for pixel-perfection 2405 translate[2] = border && /^(r|b)/i.test(corner.string()) ? 2406 BROWSER.ie === 8 ? 2 : 1 : 0; 2407 2408 // Set initial CSS 2409 inner.css({ 2410 coordsize: newSize[0]+border + ' ' + newSize[1]+border, 2411 antialias: ''+(mimic.string().indexOf(CENTER) > -1), 2412 left: translate[0] - translate[2] * Number(precedance === X), 2413 top: translate[1] - translate[2] * Number(precedance === Y), 2414 width: newSize[0] + border, 2415 height: newSize[1] + border 2416 }) 2417 .each(function(i) { 2418 var $this = $(this); 2419 2420 // Set shape specific attributes 2421 $this[ $this.prop ? 'prop' : 'attr' ]({ 2422 coordsize: newSize[0]+border + ' ' + newSize[1]+border, 2423 path: coords, 2424 fillcolor: color[0], 2425 filled: !!i, 2426 stroked: !i 2427 }) 2428 .toggle(!!(border || i)); 2429 2430 // Check if border is enabled and add stroke element 2431 !i && $this.html( createVML( 2432 'stroke', 'weight="'+border*2+'px" color="'+color[1]+'" miterlimit="1000" joinstyle="miter"' 2433 ) ); 2434 }); 2435 } 2436 2437 // Opera bug #357 - Incorrect tip position 2438 // https://github.com/Craga89/qTip2/issues/367 2439 window.opera && setTimeout(function() { 2440 elements.tip.css({ 2441 display: 'inline-block', 2442 visibility: 'visible' 2443 }); 2444 }, 1); 2445 2446 // Position if needed 2447 if(position !== FALSE) { this.calculate(corner, newSize); } 2448 }, 2449 2450 calculate: function(corner, size) { 2451 if(!this.enabled) { return FALSE; } 2452 2453 var self = this, 2454 elements = this.qtip.elements, 2455 tip = this.element, 2456 userOffset = this.options.offset, 2457 position = {}, 2458 precedance, corners; 2459 2460 // Inherit corner if not provided 2461 corner = corner || this.corner; 2462 precedance = corner.precedance; 2463 2464 // Determine which tip dimension to use for adjustment 2465 size = size || this._calculateSize(corner); 2466 2467 // Setup corners and offset array 2468 corners = [ corner.x, corner.y ]; 2469 if(precedance === X) { corners.reverse(); } 2470 2471 // Calculate tip position 2472 $.each(corners, function(i, side) { 2473 var b, bc, br; 2474 2475 if(side === CENTER) { 2476 b = precedance === Y ? LEFT : TOP; 2477 position[ b ] = '50%'; 2478 position[MARGIN+'-' + b] = -Math.round(size[ precedance === Y ? 0 : 1 ] / 2) + userOffset; 2479 } 2480 else { 2481 b = self._parseWidth(corner, side, elements.tooltip); 2482 bc = self._parseWidth(corner, side, elements.content); 2483 br = self._parseRadius(corner); 2484 2485 position[ side ] = Math.max(-self.border, i ? bc : userOffset + (br > b ? br : -b)); 2486 } 2487 }); 2488 2489 // Adjust for tip size 2490 position[ corner[precedance] ] -= size[ precedance === X ? 0 : 1 ]; 2491 2492 // Set and return new position 2493 tip.css({ margin: '', top: '', bottom: '', left: '', right: '' }).css(position); 2494 return position; 2495 }, 2496 2497 reposition: function(event, api, pos) { 2498 if(!this.enabled) { return; } 2499 2500 var cache = api.cache, 2501 newCorner = this.corner.clone(), 2502 adjust = pos.adjusted, 2503 method = api.options.position.adjust.method.split(' '), 2504 horizontal = method[0], 2505 vertical = method[1] || method[0], 2506 shift = { left: FALSE, top: FALSE, x: 0, y: 0 }, 2507 offset, css = {}, props; 2508 2509 function shiftflip(direction, precedance, popposite, side, opposite) { 2510 // Horizontal - Shift or flip method 2511 if(direction === SHIFT && newCorner.precedance === precedance && adjust[side] && newCorner[popposite] !== CENTER) { 2512 newCorner.precedance = newCorner.precedance === X ? Y : X; 2513 } 2514 else if(direction !== SHIFT && adjust[side]){ 2515 newCorner[precedance] = newCorner[precedance] === CENTER ? 2516 adjust[side] > 0 ? side : opposite : 2517 newCorner[precedance] === side ? opposite : side; 2518 } 2519 } 2520 2521 function shiftonly(xy, side, opposite) { 2522 if(newCorner[xy] === CENTER) { 2523 css[MARGIN+'-'+side] = shift[xy] = offset[MARGIN+'-'+side] - adjust[side]; 2524 } 2525 else { 2526 props = offset[opposite] !== undefined ? 2527 [ adjust[side], -offset[side] ] : [ -adjust[side], offset[side] ]; 2528 2529 if( (shift[xy] = Math.max(props[0], props[1])) > props[0] ) { 2530 pos[side] -= adjust[side]; 2531 shift[side] = FALSE; 2532 } 2533 2534 css[ offset[opposite] !== undefined ? opposite : side ] = shift[xy]; 2535 } 2536 } 2537 2538 // If our tip position isn't fixed e.g. doesn't adjust with viewport... 2539 if(this.corner.fixed !== TRUE) { 2540 // Perform shift/flip adjustments 2541 shiftflip(horizontal, X, Y, LEFT, RIGHT); 2542 shiftflip(vertical, Y, X, TOP, BOTTOM); 2543 2544 // Update and redraw the tip if needed (check cached details of last drawn tip) 2545 if(newCorner.string() !== cache.corner.string() || cache.cornerTop !== adjust.top || cache.cornerLeft !== adjust.left) { 2546 this.update(newCorner, FALSE); 2547 } 2548 } 2549 2550 // Setup tip offset properties 2551 offset = this.calculate(newCorner); 2552 2553 // Readjust offset object to make it left/top 2554 if(offset.right !== undefined) { offset.left = -offset.right; } 2555 if(offset.bottom !== undefined) { offset.top = -offset.bottom; } 2556 offset.user = this.offset; 2557 2558 // Perform shift adjustments 2559 shift.left = horizontal === SHIFT && !!adjust.left; 2560 if(shift.left) { 2561 shiftonly(X, LEFT, RIGHT); 2562 } 2563 shift.top = vertical === SHIFT && !!adjust.top; 2564 if(shift.top) { 2565 shiftonly(Y, TOP, BOTTOM); 2566 } 2567 2568 /* 2569 * If the tip is adjusted in both dimensions, or in a 2570 * direction that would cause it to be anywhere but the 2571 * outer border, hide it! 2572 */ 2573 this.element.css(css).toggle( 2574 !(shift.x && shift.y || newCorner.x === CENTER && shift.y || newCorner.y === CENTER && shift.x) 2575 ); 2576 2577 // Adjust position to accomodate tip dimensions 2578 pos.left -= offset.left.charAt ? offset.user : 2579 horizontal !== SHIFT || shift.top || !shift.left && !shift.top ? offset.left + this.border : 0; 2580 pos.top -= offset.top.charAt ? offset.user : 2581 vertical !== SHIFT || shift.left || !shift.left && !shift.top ? offset.top + this.border : 0; 2582 2583 // Cache details 2584 cache.cornerLeft = adjust.left; cache.cornerTop = adjust.top; 2585 cache.corner = newCorner.clone(); 2586 }, 2587 2588 destroy: function() { 2589 // Unbind events 2590 this.qtip._unbind(this.qtip.tooltip, this._ns); 2591 2592 // Remove the tip element(s) 2593 if(this.qtip.elements.tip) { 2594 this.qtip.elements.tip.find('*') 2595 .remove().end().remove(); 2596 } 2597 } 2598}); 2599 2600TIP = PLUGINS.tip = function(api) { 2601 return new Tip(api, api.options.style.tip); 2602}; 2603 2604// Initialize tip on render 2605TIP.initialize = 'render'; 2606 2607// Setup plugin sanitization options 2608TIP.sanitize = function(options) { 2609 if(options.style && 'tip' in options.style) { 2610 var opts = options.style.tip; 2611 if(typeof opts !== 'object') { opts = options.style.tip = { corner: opts }; } 2612 if(!(/string|boolean/i).test(typeof opts.corner)) { opts.corner = TRUE; } 2613 } 2614}; 2615 2616// Add new option checks for the plugin 2617CHECKS.tip = { 2618 '^position.my|style.tip.(corner|mimic|border)$': function() { 2619 // Make sure a tip can be drawn 2620 this.create(); 2621 2622 // Reposition the tooltip 2623 this.qtip.reposition(); 2624 }, 2625 '^style.tip.(height|width)$': function(obj) { 2626 // Re-set dimensions and redraw the tip 2627 this.size = [ obj.width, obj.height ]; 2628 this.update(); 2629 2630 // Reposition the tooltip 2631 this.qtip.reposition(); 2632 }, 2633 '^content.title|style.(classes|widget)$': function() { 2634 this.update(); 2635 } 2636}; 2637 2638// Extend original qTip defaults 2639$.extend(TRUE, QTIP.defaults, { 2640 style: { 2641 tip: { 2642 corner: TRUE, 2643 mimic: FALSE, 2644 width: 6, 2645 height: 6, 2646 border: TRUE, 2647 offset: 0 2648 } 2649 } 2650}); 2651;var MODAL, OVERLAY, 2652 MODALCLASS = 'qtip-modal', 2653 MODALSELECTOR = '.'+MODALCLASS; 2654 2655OVERLAY = function() 2656{ 2657 var self = this, 2658 focusableElems = {}, 2659 current, 2660 prevState, 2661 elem; 2662 2663 // Modified code from jQuery UI 1.10.0 source 2664 // http://code.jquery.com/ui/1.10.0/jquery-ui.js 2665 function focusable(element) { 2666 // Use the defined focusable checker when possible 2667 if($.expr[':'].focusable) { return $.expr[':'].focusable; } 2668 2669 var isTabIndexNotNaN = !isNaN($.attr(element, 'tabindex')), 2670 nodeName = element.nodeName && element.nodeName.toLowerCase(), 2671 map, mapName, img; 2672 2673 if('area' === nodeName) { 2674 map = element.parentNode; 2675 mapName = map.name; 2676 if(!element.href || !mapName || map.nodeName.toLowerCase() !== 'map') { 2677 return false; 2678 } 2679 img = $('img[usemap=#' + mapName + ']')[0]; 2680 return !!img && img.is(':visible'); 2681 } 2682 2683 return /input|select|textarea|button|object/.test( nodeName ) ? 2684 !element.disabled : 2685 'a' === nodeName ? 2686 element.href || isTabIndexNotNaN : 2687 isTabIndexNotNaN 2688 ; 2689 } 2690 2691 // Focus inputs using cached focusable elements (see update()) 2692 function focusInputs(blurElems) { 2693 // Blurring body element in IE causes window.open windows to unfocus! 2694 if(focusableElems.length < 1 && blurElems.length) { blurElems.not('body').blur(); } 2695 2696 // Focus the inputs 2697 else { focusableElems.first().focus(); } 2698 } 2699 2700 // Steal focus from elements outside tooltip 2701 function stealFocus(event) { 2702 if(!elem.is(':visible')) { return; } 2703 2704 var target = $(event.target), 2705 tooltip = current.tooltip, 2706 container = target.closest(SELECTOR), 2707 targetOnTop; 2708 2709 // Determine if input container target is above this 2710 targetOnTop = container.length < 1 ? FALSE : 2711 parseInt(container[0].style.zIndex, 10) > parseInt(tooltip[0].style.zIndex, 10); 2712 2713 // If we're showing a modal, but focus has landed on an input below 2714 // this modal, divert focus to the first visible input in this modal 2715 // or if we can't find one... the tooltip itself 2716 if(!targetOnTop && target.closest(SELECTOR)[0] !== tooltip[0]) { 2717 focusInputs(target); 2718 } 2719 } 2720 2721 $.extend(self, { 2722 init: function() { 2723 // Create document overlay 2724 elem = self.elem = $('<div />', { 2725 id: 'qtip-overlay', 2726 html: '<div></div>', 2727 mousedown: function() { return FALSE; } 2728 }) 2729 .hide(); 2730 2731 // Make sure we can't focus anything outside the tooltip 2732 $(document.body).bind('focusin'+MODALSELECTOR, stealFocus); 2733 2734 // Apply keyboard "Escape key" close handler 2735 $(document).bind('keydown'+MODALSELECTOR, function(event) { 2736 if(current && current.options.show.modal.escape && event.keyCode === 27) { 2737 current.hide(event); 2738 } 2739 }); 2740 2741 // Apply click handler for blur option 2742 elem.bind('click'+MODALSELECTOR, function(event) { 2743 if(current && current.options.show.modal.blur) { 2744 current.hide(event); 2745 } 2746 }); 2747 2748 return self; 2749 }, 2750 2751 update: function(api) { 2752 // Update current API reference 2753 current = api; 2754 2755 // Update focusable elements if enabled 2756 if(api.options.show.modal.stealfocus !== FALSE) { 2757 focusableElems = api.tooltip.find('*').filter(function() { 2758 return focusable(this); 2759 }); 2760 } 2761 else { focusableElems = []; } 2762 }, 2763 2764 toggle: function(api, state, duration) { 2765 var tooltip = api.tooltip, 2766 options = api.options.show.modal, 2767 effect = options.effect, 2768 type = state ? 'show': 'hide', 2769 visible = elem.is(':visible'), 2770 visibleModals = $(MODALSELECTOR).filter(':visible:not(:animated)').not(tooltip); 2771 2772 // Set active tooltip API reference 2773 self.update(api); 2774 2775 // If the modal can steal the focus... 2776 // Blur the current item and focus anything in the modal we an 2777 if(state && options.stealfocus !== FALSE) { 2778 focusInputs( $(':focus') ); 2779 } 2780 2781 // Toggle backdrop cursor style on show 2782 elem.toggleClass('blurs', options.blur); 2783 2784 // Append to body on show 2785 if(state) { 2786 elem.appendTo(document.body); 2787 } 2788 2789 // Prevent modal from conflicting with show.solo, and don't hide backdrop is other modals are visible 2790 if(elem.is(':animated') && visible === state && prevState !== FALSE || !state && visibleModals.length) { 2791 return self; 2792 } 2793 2794 // Stop all animations 2795 elem.stop(TRUE, FALSE); 2796 2797 // Use custom function if provided 2798 if($.isFunction(effect)) { 2799 effect.call(elem, state); 2800 } 2801 2802 // If no effect type is supplied, use a simple toggle 2803 else if(effect === FALSE) { 2804 elem[ type ](); 2805 } 2806 2807 // Use basic fade function 2808 else { 2809 elem.fadeTo( parseInt(duration, 10) || 90, state ? 1 : 0, function() { 2810 if(!state) { elem.hide(); } 2811 }); 2812 } 2813 2814 // Reset position and detach from body on hide 2815 if(!state) { 2816 elem.queue(function(next) { 2817 elem.css({ left: '', top: '' }); 2818 if(!$(MODALSELECTOR).length) { elem.detach(); } 2819 next(); 2820 }); 2821 } 2822 2823 // Cache the state 2824 prevState = state; 2825 2826 // If the tooltip is destroyed, set reference to null 2827 if(current.destroyed) { current = NULL; } 2828 2829 return self; 2830 } 2831 }); 2832 2833 self.init(); 2834}; 2835OVERLAY = new OVERLAY(); 2836 2837function Modal(api, options) { 2838 this.options = options; 2839 this._ns = '-modal'; 2840 2841 this.qtip = api; 2842 this.init(api); 2843} 2844 2845$.extend(Modal.prototype, { 2846 init: function(qtip) { 2847 var tooltip = qtip.tooltip; 2848 2849 // If modal is disabled... return 2850 if(!this.options.on) { return this; } 2851 2852 // Set overlay reference 2853 qtip.elements.overlay = OVERLAY.elem; 2854 2855 // Add unique attribute so we can grab modal tooltips easily via a SELECTOR, and set z-index 2856 tooltip.addClass(MODALCLASS).css('z-index', QTIP.modal_zindex + $(MODALSELECTOR).length); 2857 2858 // Apply our show/hide/focus modal events 2859 qtip._bind(tooltip, ['tooltipshow', 'tooltiphide'], function(event, api, duration) { 2860 var oEvent = event.originalEvent; 2861 2862 // Make sure mouseout doesn't trigger a hide when showing the modal and mousing onto backdrop 2863 if(event.target === tooltip[0]) { 2864 if(oEvent && event.type === 'tooltiphide' && /mouse(leave|enter)/.test(oEvent.type) && $(oEvent.relatedTarget).closest(OVERLAY.elem[0]).length) { 2865 /* eslint-disable no-empty */ 2866 try { event.preventDefault(); } 2867 catch(e) {} 2868 /* eslint-enable no-empty */ 2869 } 2870 else if(!oEvent || oEvent && oEvent.type !== 'tooltipsolo') { 2871 this.toggle(event, event.type === 'tooltipshow', duration); 2872 } 2873 } 2874 }, this._ns, this); 2875 2876 // Adjust modal z-index on tooltip focus 2877 qtip._bind(tooltip, 'tooltipfocus', function(event, api) { 2878 // If focus was cancelled before it reached us, don't do anything 2879 if(event.isDefaultPrevented() || event.target !== tooltip[0]) { return; } 2880 2881 var qtips = $(MODALSELECTOR), 2882 2883 // Keep the modal's lower than other, regular qtips 2884 newIndex = QTIP.modal_zindex + qtips.length, 2885 curIndex = parseInt(tooltip[0].style.zIndex, 10); 2886 2887 // Set overlay z-index 2888 OVERLAY.elem[0].style.zIndex = newIndex - 1; 2889 2890 // Reduce modal z-index's and keep them properly ordered 2891 qtips.each(function() { 2892 if(this.style.zIndex > curIndex) { 2893 this.style.zIndex -= 1; 2894 } 2895 }); 2896 2897 // Fire blur event for focused tooltip 2898 qtips.filter('.' + CLASS_FOCUS).qtip('blur', event.originalEvent); 2899 2900 // Set the new z-index 2901 tooltip.addClass(CLASS_FOCUS)[0].style.zIndex = newIndex; 2902 2903 // Set current 2904 OVERLAY.update(api); 2905 2906 // Prevent default handling 2907 /* eslint-disable no-empty */ 2908 try { event.preventDefault(); } 2909 catch(e) {} 2910 /* eslint-enable no-empty */ 2911 }, this._ns, this); 2912 2913 // Focus any other visible modals when this one hides 2914 qtip._bind(tooltip, 'tooltiphide', function(event) { 2915 if(event.target === tooltip[0]) { 2916 $(MODALSELECTOR).filter(':visible').not(tooltip).last().qtip('focus', event); 2917 } 2918 }, this._ns, this); 2919 }, 2920 2921 toggle: function(event, state, duration) { 2922 // Make sure default event hasn't been prevented 2923 if(event && event.isDefaultPrevented()) { return this; } 2924 2925 // Toggle it 2926 OVERLAY.toggle(this.qtip, !!state, duration); 2927 }, 2928 2929 destroy: function() { 2930 // Remove modal class 2931 this.qtip.tooltip.removeClass(MODALCLASS); 2932 2933 // Remove bound events 2934 this.qtip._unbind(this.qtip.tooltip, this._ns); 2935 2936 // Delete element reference 2937 OVERLAY.toggle(this.qtip, FALSE); 2938 delete this.qtip.elements.overlay; 2939 } 2940}); 2941 2942 2943MODAL = PLUGINS.modal = function(api) { 2944 return new Modal(api, api.options.show.modal); 2945}; 2946 2947// Setup sanitiztion rules 2948MODAL.sanitize = function(opts) { 2949 if(opts.show) { 2950 if(typeof opts.show.modal !== 'object') { opts.show.modal = { on: !!opts.show.modal }; } 2951 else if(typeof opts.show.modal.on === 'undefined') { opts.show.modal.on = TRUE; } 2952 } 2953}; 2954 2955// Base z-index for all modal tooltips (use qTip core z-index as a base) 2956/* eslint-disable camelcase */ 2957QTIP.modal_zindex = QTIP.zindex - 200; 2958/* eslint-enable camelcase */ 2959 2960// Plugin needs to be initialized on render 2961MODAL.initialize = 'render'; 2962 2963// Setup option set checks 2964CHECKS.modal = { 2965 '^show.modal.(on|blur)$': function() { 2966 // Initialise 2967 this.destroy(); 2968 this.init(); 2969 2970 // Show the modal if not visible already and tooltip is visible 2971 this.qtip.elems.overlay.toggle( 2972 this.qtip.tooltip[0].offsetWidth > 0 2973 ); 2974 } 2975}; 2976 2977// Extend original api defaults 2978$.extend(TRUE, QTIP.defaults, { 2979 show: { 2980 modal: { 2981 on: FALSE, 2982 effect: TRUE, 2983 blur: TRUE, 2984 stealfocus: TRUE, 2985 escape: TRUE 2986 } 2987 } 2988}); 2989;PLUGINS.viewport = function(api, position, posOptions, targetWidth, targetHeight, elemWidth, elemHeight) 2990{ 2991 var target = posOptions.target, 2992 tooltip = api.elements.tooltip, 2993 my = posOptions.my, 2994 at = posOptions.at, 2995 adjust = posOptions.adjust, 2996 method = adjust.method.split(' '), 2997 methodX = method[0], 2998 methodY = method[1] || method[0], 2999 viewport = posOptions.viewport, 3000 container = posOptions.container, 3001 adjusted = { left: 0, top: 0 }, 3002 fixed, newMy, containerOffset, containerStatic, 3003 viewportWidth, viewportHeight, viewportScroll, viewportOffset; 3004 3005 // If viewport is not a jQuery element, or it's the window/document, or no adjustment method is used... return 3006 if(!viewport.jquery || target[0] === window || target[0] === document.body || adjust.method === 'none') { 3007 return adjusted; 3008 } 3009 3010 // Cach container details 3011 containerOffset = container.offset() || adjusted; 3012 containerStatic = container.css('position') === 'static'; 3013 3014 // Cache our viewport details 3015 fixed = tooltip.css('position') === 'fixed'; 3016 viewportWidth = viewport[0] === window ? viewport.width() : viewport.outerWidth(FALSE); 3017 viewportHeight = viewport[0] === window ? viewport.height() : viewport.outerHeight(FALSE); 3018 viewportScroll = { left: fixed ? 0 : viewport.scrollLeft(), top: fixed ? 0 : viewport.scrollTop() }; 3019 viewportOffset = viewport.offset() || adjusted; 3020 3021 // Generic calculation method 3022 function calculate(side, otherSide, type, adjustment, side1, side2, lengthName, targetLength, elemLength) { 3023 var initialPos = position[side1], 3024 mySide = my[side], 3025 atSide = at[side], 3026 isShift = type === SHIFT, 3027 myLength = mySide === side1 ? elemLength : mySide === side2 ? -elemLength : -elemLength / 2, 3028 atLength = atSide === side1 ? targetLength : atSide === side2 ? -targetLength : -targetLength / 2, 3029 sideOffset = viewportScroll[side1] + viewportOffset[side1] - (containerStatic ? 0 : containerOffset[side1]), 3030 overflow1 = sideOffset - initialPos, 3031 overflow2 = initialPos + elemLength - (lengthName === WIDTH ? viewportWidth : viewportHeight) - sideOffset, 3032 offset = myLength - (my.precedance === side || mySide === my[otherSide] ? atLength : 0) - (atSide === CENTER ? targetLength / 2 : 0); 3033 3034 // shift 3035 if(isShift) { 3036 offset = (mySide === side1 ? 1 : -1) * myLength; 3037 3038 // Adjust position but keep it within viewport dimensions 3039 position[side1] += overflow1 > 0 ? overflow1 : overflow2 > 0 ? -overflow2 : 0; 3040 position[side1] = Math.max( 3041 -containerOffset[side1] + viewportOffset[side1], 3042 initialPos - offset, 3043 Math.min( 3044 Math.max( 3045 -containerOffset[side1] + viewportOffset[side1] + (lengthName === WIDTH ? viewportWidth : viewportHeight), 3046 initialPos + offset 3047 ), 3048 position[side1], 3049 3050 // Make sure we don't adjust complete off the element when using 'center' 3051 mySide === 'center' ? initialPos - myLength : 1E9 3052 ) 3053 ); 3054 3055 } 3056 3057 // flip/flipinvert 3058 else { 3059 // Update adjustment amount depending on if using flipinvert or flip 3060 adjustment *= type === FLIPINVERT ? 2 : 0; 3061 3062 // Check for overflow on the left/top 3063 if(overflow1 > 0 && (mySide !== side1 || overflow2 > 0)) { 3064 position[side1] -= offset + adjustment; 3065 newMy.invert(side, side1); 3066 } 3067 3068 // Check for overflow on the bottom/right 3069 else if(overflow2 > 0 && (mySide !== side2 || overflow1 > 0) ) { 3070 position[side1] -= (mySide === CENTER ? -offset : offset) + adjustment; 3071 newMy.invert(side, side2); 3072 } 3073 3074 // Make sure we haven't made things worse with the adjustment and reset if so 3075 if(position[side1] < viewportScroll[side1] && -position[side1] > overflow2) { 3076 position[side1] = initialPos; newMy = my.clone(); 3077 } 3078 } 3079 3080 return position[side1] - initialPos; 3081 } 3082 3083 // Set newMy if using flip or flipinvert methods 3084 if(methodX !== 'shift' || methodY !== 'shift') { newMy = my.clone(); } 3085 3086 // Adjust position based onviewport and adjustment options 3087 adjusted = { 3088 left: methodX !== 'none' ? calculate( X, Y, methodX, adjust.x, LEFT, RIGHT, WIDTH, targetWidth, elemWidth ) : 0, 3089 top: methodY !== 'none' ? calculate( Y, X, methodY, adjust.y, TOP, BOTTOM, HEIGHT, targetHeight, elemHeight ) : 0, 3090 my: newMy 3091 }; 3092 3093 return adjusted; 3094}; 3095;PLUGINS.polys = { 3096 // POLY area coordinate calculator 3097 // Special thanks to Ed Cradock for helping out with this. 3098 // Uses a binary search algorithm to find suitable coordinates. 3099 polygon: function(baseCoords, corner) { 3100 var result = { 3101 width: 0, height: 0, 3102 position: { 3103 top: 1e10, right: 0, 3104 bottom: 0, left: 1e10 3105 }, 3106 adjustable: FALSE 3107 }, 3108 i = 0, next, 3109 coords = [], 3110 compareX = 1, compareY = 1, 3111 realX = 0, realY = 0, 3112 newWidth, newHeight; 3113 3114 // First pass, sanitize coords and determine outer edges 3115 i = baseCoords.length; 3116 while(i--) { 3117 next = [ parseInt(baseCoords[--i], 10), parseInt(baseCoords[i+1], 10) ]; 3118 3119 if(next[0] > result.position.right){ result.position.right = next[0]; } 3120 if(next[0] < result.position.left){ result.position.left = next[0]; } 3121 if(next[1] > result.position.bottom){ result.position.bottom = next[1]; } 3122 if(next[1] < result.position.top){ result.position.top = next[1]; } 3123 3124 coords.push(next); 3125 } 3126 3127 // Calculate height and width from outer edges 3128 newWidth = result.width = Math.abs(result.position.right - result.position.left); 3129 newHeight = result.height = Math.abs(result.position.bottom - result.position.top); 3130 3131 // If it's the center corner... 3132 if(corner.abbrev() === 'c') { 3133 result.position = { 3134 left: result.position.left + result.width / 2, 3135 top: result.position.top + result.height / 2 3136 }; 3137 } 3138 else { 3139 // Second pass, use a binary search algorithm to locate most suitable coordinate 3140 while(newWidth > 0 && newHeight > 0 && compareX > 0 && compareY > 0) 3141 { 3142 newWidth = Math.floor(newWidth / 2); 3143 newHeight = Math.floor(newHeight / 2); 3144 3145 if(corner.x === LEFT){ compareX = newWidth; } 3146 else if(corner.x === RIGHT){ compareX = result.width - newWidth; } 3147 else{ compareX += Math.floor(newWidth / 2); } 3148 3149 if(corner.y === TOP){ compareY = newHeight; } 3150 else if(corner.y === BOTTOM){ compareY = result.height - newHeight; } 3151 else{ compareY += Math.floor(newHeight / 2); } 3152 3153 i = coords.length; 3154 while(i--) 3155 { 3156 if(coords.length < 2){ break; } 3157 3158 realX = coords[i][0] - result.position.left; 3159 realY = coords[i][1] - result.position.top; 3160 3161 if( 3162 corner.x === LEFT && realX >= compareX || 3163 corner.x === RIGHT && realX <= compareX || 3164 corner.x === CENTER && (realX < compareX || realX > result.width - compareX) || 3165 corner.y === TOP && realY >= compareY || 3166 corner.y === BOTTOM && realY <= compareY || 3167 corner.y === CENTER && (realY < compareY || realY > result.height - compareY)) { 3168 coords.splice(i, 1); 3169 } 3170 } 3171 } 3172 result.position = { left: coords[0][0], top: coords[0][1] }; 3173 } 3174 3175 return result; 3176 }, 3177 3178 rect: function(ax, ay, bx, by) { 3179 return { 3180 width: Math.abs(bx - ax), 3181 height: Math.abs(by - ay), 3182 position: { 3183 left: Math.min(ax, bx), 3184 top: Math.min(ay, by) 3185 } 3186 }; 3187 }, 3188 3189 _angles: { 3190 tc: 3 / 2, tr: 7 / 4, tl: 5 / 4, 3191 bc: 1 / 2, br: 1 / 4, bl: 3 / 4, 3192 rc: 2, lc: 1, c: 0 3193 }, 3194 ellipse: function(cx, cy, rx, ry, corner) { 3195 var c = PLUGINS.polys._angles[ corner.abbrev() ], 3196 rxc = c === 0 ? 0 : rx * Math.cos( c * Math.PI ), 3197 rys = ry * Math.sin( c * Math.PI ); 3198 3199 return { 3200 width: rx * 2 - Math.abs(rxc), 3201 height: ry * 2 - Math.abs(rys), 3202 position: { 3203 left: cx + rxc, 3204 top: cy + rys 3205 }, 3206 adjustable: FALSE 3207 }; 3208 }, 3209 circle: function(cx, cy, r, corner) { 3210 return PLUGINS.polys.ellipse(cx, cy, r, r, corner); 3211 } 3212}; 3213;PLUGINS.svg = function(api, svg, corner) 3214{ 3215 var elem = svg[0], 3216 root = $(elem.ownerSVGElement), 3217 ownerDocument = elem.ownerDocument, 3218 strokeWidth2 = (parseInt(svg.css('stroke-width'), 10) || 0) / 2, 3219 frameOffset, mtx, transformed, 3220 len, next, i, points, 3221 result, position; 3222 3223 // Ascend the parentNode chain until we find an element with getBBox() 3224 while(!elem.getBBox) { elem = elem.parentNode; } 3225 if(!elem.getBBox || !elem.parentNode) { return FALSE; } 3226 3227 // Determine which shape calculation to use 3228 switch(elem.nodeName) { 3229 case 'ellipse': 3230 case 'circle': 3231 result = PLUGINS.polys.ellipse( 3232 elem.cx.baseVal.value, 3233 elem.cy.baseVal.value, 3234 (elem.rx || elem.r).baseVal.value + strokeWidth2, 3235 (elem.ry || elem.r).baseVal.value + strokeWidth2, 3236 corner 3237 ); 3238 break; 3239 3240 case 'line': 3241 case 'polygon': 3242 case 'polyline': 3243 // Determine points object (line has none, so mimic using array) 3244 points = elem.points || [ 3245 { x: elem.x1.baseVal.value, y: elem.y1.baseVal.value }, 3246 { x: elem.x2.baseVal.value, y: elem.y2.baseVal.value } 3247 ]; 3248 3249 for(result = [], i = -1, len = points.numberOfItems || points.length; ++i < len;) { 3250 next = points.getItem ? points.getItem(i) : points[i]; 3251 result.push.apply(result, [next.x, next.y]); 3252 } 3253 3254 result = PLUGINS.polys.polygon(result, corner); 3255 break; 3256 3257 // Unknown shape or rectangle? Use bounding box 3258 default: 3259 result = elem.getBBox(); 3260 result = { 3261 width: result.width, 3262 height: result.height, 3263 position: { 3264 left: result.x, 3265 top: result.y 3266 } 3267 }; 3268 break; 3269 } 3270 3271 // Shortcut assignments 3272 position = result.position; 3273 root = root[0]; 3274 3275 // Convert position into a pixel value 3276 if(root.createSVGPoint) { 3277 mtx = elem.getScreenCTM(); 3278 points = root.createSVGPoint(); 3279 3280 points.x = position.left; 3281 points.y = position.top; 3282 transformed = points.matrixTransform( mtx ); 3283 position.left = transformed.x; 3284 position.top = transformed.y; 3285 } 3286 3287 // Check the element is not in a child document, and if so, adjust for frame elements offset 3288 if(ownerDocument !== document && api.position.target !== 'mouse') { 3289 frameOffset = $((ownerDocument.defaultView || ownerDocument.parentWindow).frameElement).offset(); 3290 if(frameOffset) { 3291 position.left += frameOffset.left; 3292 position.top += frameOffset.top; 3293 } 3294 } 3295 3296 // Adjust by scroll offset of owner document 3297 ownerDocument = $(ownerDocument); 3298 position.left += ownerDocument.scrollLeft(); 3299 position.top += ownerDocument.scrollTop(); 3300 3301 return result; 3302}; 3303;PLUGINS.imagemap = function(api, area, corner) 3304{ 3305 if(!area.jquery) { area = $(area); } 3306 3307 var shape = (area.attr('shape') || 'rect').toLowerCase().replace('poly', 'polygon'), 3308 image = $('img[usemap="#'+area.parent('map').attr('name')+'"]'), 3309 coordsString = $.trim(area.attr('coords')), 3310 coordsArray = coordsString.replace(/,$/, '').split(','), 3311 imageOffset, coords, i, result, len; 3312 3313 // If we can't find the image using the map... 3314 if(!image.length) { return FALSE; } 3315 3316 // Pass coordinates string if polygon 3317 if(shape === 'polygon') { 3318 result = PLUGINS.polys.polygon(coordsArray, corner); 3319 } 3320 3321 // Otherwise parse the coordinates and pass them as arguments 3322 else if(PLUGINS.polys[shape]) { 3323 for(i = -1, len = coordsArray.length, coords = []; ++i < len;) { 3324 coords.push( parseInt(coordsArray[i], 10) ); 3325 } 3326 3327 result = PLUGINS.polys[shape].apply( 3328 this, coords.concat(corner) 3329 ); 3330 } 3331 3332 // If no shapre calculation method was found, return false 3333 else { return FALSE; } 3334 3335 // Make sure we account for padding and borders on the image 3336 imageOffset = image.offset(); 3337 imageOffset.left += Math.ceil((image.outerWidth(FALSE) - image.width()) / 2); 3338 imageOffset.top += Math.ceil((image.outerHeight(FALSE) - image.height()) / 2); 3339 3340 // Add image position to offset coordinates 3341 result.position.left += imageOffset.left; 3342 result.position.top += imageOffset.top; 3343 3344 return result; 3345}; 3346;var IE6, 3347 3348/* 3349 * BGIFrame adaption (http://plugins.jquery.com/project/bgiframe) 3350 * Special thanks to Brandon Aaron 3351 */ 3352BGIFRAME = '<iframe class="qtip-bgiframe" frameborder="0" tabindex="-1" src="javascript:\'\';" ' + 3353 ' style="display:block; position:absolute; z-index:-1; filter:alpha(opacity=0); ' + 3354 '-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";"></iframe>'; 3355 3356function Ie6(api) { 3357 this._ns = 'ie6'; 3358 3359 this.qtip = api; 3360 this.init(api); 3361} 3362 3363$.extend(Ie6.prototype, { 3364 _scroll : function() { 3365 var overlay = this.qtip.elements.overlay; 3366 overlay && (overlay[0].style.top = $(window).scrollTop() + 'px'); 3367 }, 3368 3369 init: function(qtip) { 3370 var tooltip = qtip.tooltip; 3371 3372 // Create the BGIFrame element if needed 3373 if($('select, object').length < 1) { 3374 this.bgiframe = qtip.elements.bgiframe = $(BGIFRAME).appendTo(tooltip); 3375 3376 // Update BGIFrame on tooltip move 3377 qtip._bind(tooltip, 'tooltipmove', this.adjustBGIFrame, this._ns, this); 3378 } 3379 3380 // redraw() container for width/height calculations 3381 this.redrawContainer = $('<div/>', { id: NAMESPACE+'-rcontainer' }) 3382 .appendTo(document.body); 3383 3384 // Fixup modal plugin if present too 3385 if( qtip.elements.overlay && qtip.elements.overlay.addClass('qtipmodal-ie6fix') ) { 3386 qtip._bind(window, ['scroll', 'resize'], this._scroll, this._ns, this); 3387 qtip._bind(tooltip, ['tooltipshow'], this._scroll, this._ns, this); 3388 } 3389 3390 // Set dimensions 3391 this.redraw(); 3392 }, 3393 3394 adjustBGIFrame: function() { 3395 var tooltip = this.qtip.tooltip, 3396 dimensions = { 3397 height: tooltip.outerHeight(FALSE), 3398 width: tooltip.outerWidth(FALSE) 3399 }, 3400 plugin = this.qtip.plugins.tip, 3401 tip = this.qtip.elements.tip, 3402 tipAdjust, offset; 3403 3404 // Adjust border offset 3405 offset = parseInt(tooltip.css('borderLeftWidth'), 10) || 0; 3406 offset = { left: -offset, top: -offset }; 3407 3408 // Adjust for tips plugin 3409 if(plugin && tip) { 3410 tipAdjust = plugin.corner.precedance === 'x' ? [WIDTH, LEFT] : [HEIGHT, TOP]; 3411 offset[ tipAdjust[1] ] -= tip[ tipAdjust[0] ](); 3412 } 3413 3414 // Update bgiframe 3415 this.bgiframe.css(offset).css(dimensions); 3416 }, 3417 3418 // Max/min width simulator function 3419 redraw: function() { 3420 if(this.qtip.rendered < 1 || this.drawing) { return this; } 3421 3422 var tooltip = this.qtip.tooltip, 3423 style = this.qtip.options.style, 3424 container = this.qtip.options.position.container, 3425 perc, width, max, min; 3426 3427 // Set drawing flag 3428 this.qtip.drawing = 1; 3429 3430 // If tooltip has a set height/width, just set it... like a boss! 3431 if(style.height) { tooltip.css(HEIGHT, style.height); } 3432 if(style.width) { tooltip.css(WIDTH, style.width); } 3433 3434 // Simulate max/min width if not set width present... 3435 else { 3436 // Reset width and add fluid class 3437 tooltip.css(WIDTH, '').appendTo(this.redrawContainer); 3438 3439 // Grab our tooltip width (add 1 if odd so we don't get wrapping problems.. huzzah!) 3440 width = tooltip.width(); 3441 if(width % 2 < 1) { width += 1; } 3442 3443 // Grab our max/min properties 3444 max = tooltip.css('maxWidth') || ''; 3445 min = tooltip.css('minWidth') || ''; 3446 3447 // Parse into proper pixel values 3448 perc = (max + min).indexOf('%') > -1 ? container.width() / 100 : 0; 3449 max = (max.indexOf('%') > -1 ? perc : 1 * parseInt(max, 10)) || width; 3450 min = (min.indexOf('%') > -1 ? perc : 1 * parseInt(min, 10)) || 0; 3451 3452 // Determine new dimension size based on max/min/current values 3453 width = max + min ? Math.min(Math.max(width, min), max) : width; 3454 3455 // Set the newly calculated width and remvoe fluid class 3456 tooltip.css(WIDTH, Math.round(width)).appendTo(container); 3457 } 3458 3459 // Set drawing flag 3460 this.drawing = 0; 3461 3462 return this; 3463 }, 3464 3465 destroy: function() { 3466 // Remove iframe 3467 this.bgiframe && this.bgiframe.remove(); 3468 3469 // Remove bound events 3470 this.qtip._unbind([window, this.qtip.tooltip], this._ns); 3471 } 3472}); 3473 3474IE6 = PLUGINS.ie6 = function(api) { 3475 // Proceed only if the browser is IE6 3476 return BROWSER.ie === 6 ? new Ie6(api) : FALSE; 3477}; 3478 3479IE6.initialize = 'render'; 3480 3481CHECKS.ie6 = { 3482 '^content|style$': function() { 3483 this.redraw(); 3484 } 3485}; 3486;})); 3487}( window, document ));
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.