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: Thu May 12 2016 08:48 EDT-0400 10 * Plugins: tips viewport imagemap svg modal 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
vendor: 16,345 bytes, lines 25-647
25); 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
647MT ? element.imagesLoadedMT
vendor: 12,532 bytes, lines 647-1034
647() : 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
vendor: 9,977 bytes, lines 1035-1387
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
1387//flannian fix hover cannot be binded issue. 1388 if(events=='hover') {events = 'mouseenter';}
vendor: 11,980 bytes, lines 1388-1731
1388 1389 $(targets).bind( 1390 (events.split ? events : events.join(ns + ' ')) + ns, 1391 $.proxy(method, context || this) 1392 ); 1393 return this; 1394}; 1395PROTOTYPE._unbind = function(targets, suffix) { 1396 targets && $(targets).unbind('.' + this._id + (suffix ? '-'+suffix : '')); 1397 return this; 1398}; 1399 1400// Global delegation helper 1401function delegate(selector, events, method) { 1402 $(document.body).delegate(selector, 1403 (events.split ? events : events.join('.'+NAMESPACE + ' ')) + '.'+NAMESPACE, 1404 function() { 1405 var api = QTIP.api[ $.attr(this, ATTR_ID) ]; 1406 api && !api.disabled && method.apply(api, arguments); 1407 } 1408 ); 1409} 1410// Event trigger 1411PROTOTYPE._trigger = function(type, args, event) { 1412 var callback = new $.Event('tooltip'+type); 1413 callback.originalEvent = event && $.extend({}, event) || this.cache.event || NULL; 1414 1415 this.triggering = type; 1416 this.tooltip.trigger(callback, [this].concat(args || [])); 1417 this.triggering = FALSE; 1418 1419 return !callback.isDefaultPrevented(); 1420}; 1421 1422PROTOTYPE._bindEvents = function(showEvents, hideEvents, showTargets, hideTargets, showCallback, hideCallback) { 1423 // Get tasrgets that lye within both 1424 var similarTargets = showTargets.filter( hideTargets ).add( hideTargets.filter(showTargets) ), 1425 toggleEvents = []; 1426 1427 // If hide and show targets are the same... 1428 if(similarTargets.length) { 1429 1430 // Filter identical show/hide events 1431 $.each(hideEvents, function(i, type) { 1432 var showIndex = $.inArray(type, showEvents); 1433 1434 // Both events are identical, remove from both hide and show events 1435 // and append to toggleEvents 1436 showIndex > -1 && toggleEvents.push( showEvents.splice( showIndex, 1 )[0] ); 1437 }); 1438 1439 // Toggle events are special case of identical show/hide events, which happen in sequence 1440 if(toggleEvents.length) { 1441 // Bind toggle events to the similar targets 1442 this._bind(similarTargets, toggleEvents, function(event) { 1443 var state = this.rendered ? this.tooltip[0].offsetWidth > 0 : false; 1444 (state ? hideCallback : showCallback).call(this, event); 1445 }); 1446 1447 // Remove the similar targets from the regular show/hide bindings 1448 showTargets = showTargets.not(similarTargets); 1449 hideTargets = hideTargets.not(similarTargets); 1450 } 1451 } 1452 1453 // Apply show/hide/toggle events 1454 this._bind(showTargets, showEvents, showCallback); 1455 this._bind(hideTargets, hideEvents, hideCallback); 1456}; 1457 1458PROTOTYPE._assignInitialEvents = function(event) { 1459 var options = this.options, 1460 showTarget = options.show.target, 1461 hideTarget = options.hide.target, 1462 showEvents = options.show.event ? $.trim('' + options.show.event).split(' ') : [], 1463 hideEvents = options.hide.event ? $.trim('' + options.hide.event).split(' ') : []; 1464 1465 // Catch remove/removeqtip events on target element to destroy redundant tooltips 1466 this._bind(this.elements.target, ['remove', 'removeqtip'], function() { 1467 this.destroy(true); 1468 }, 'destroy'); 1469 1470 /* 1471 * Make sure hoverIntent functions properly by using mouseleave as a hide event if 1472 * mouseenter/mouseout is used for show.event, even if it isn't in the users options. 1473 */ 1474 if(/mouse(over|enter)/i.test(options.show.event) && !/mouse(out|leave)/i.test(options.hide.event)) { 1475 hideEvents.push('mouseleave'); 1476 } 1477 1478 /* 1479 * Also make sure initial mouse targetting works correctly by caching mousemove coords 1480 * on show targets before the tooltip has rendered. Also set onTarget when triggered to 1481 * keep mouse tracking working. 1482 */ 1483 this._bind(showTarget, 'mousemove', function(moveEvent) { 1484 this._storeMouse(moveEvent); 1485 this.cache.onTarget = TRUE; 1486 }); 1487 1488 // Define hoverIntent function 1489 function hoverIntent(hoverEvent) { 1490 // Only continue if tooltip isn't disabled 1491 if(this.disabled || this.destroyed) { return FALSE; } 1492 1493 // Cache the event data 1494 this.cache.event = hoverEvent && $.event.fix(hoverEvent); 1495 this.cache.target = hoverEvent && $(hoverEvent.target); 1496 1497 // Start the event sequence 1498 clearTimeout(this.timers.show); 1499 this.timers.show = delay.call(this, 1500 function() { this.render(typeof hoverEvent === 'object' || options.show.ready); }, 1501 options.prerender ? 0 : options.show.delay 1502 ); 1503 } 1504 1505 // Filter and bind events 1506 this._bindEvents(showEvents, hideEvents, showTarget, hideTarget, hoverIntent, function() { 1507 if(!this.timers) { return FALSE; } 1508 clearTimeout(this.timers.show); 1509 }); 1510 1511 // Prerendering is enabled, create tooltip now 1512 if(options.show.ready || options.prerender) { hoverIntent.call(this, event); } 1513}; 1514 1515// Event assignment method 1516PROTOTYPE._assignEvents = function() { 1517 var self = this, 1518 options = this.options, 1519 posOptions = options.position, 1520 1521 tooltip = this.tooltip, 1522 showTarget = options.show.target, 1523 hideTarget = options.hide.target, 1524 containerTarget = posOptions.container, 1525 viewportTarget = posOptions.viewport, 1526 documentTarget = $(document), 1527 windowTarget = $(window), 1528 1529 showEvents = options.show.event ? $.trim('' + options.show.event).split(' ') : [], 1530 hideEvents = options.hide.event ? $.trim('' + options.hide.event).split(' ') : []; 1531 1532 1533 // Assign passed event callbacks 1534 $.each(options.events, function(name, callback) { 1535 self._bind(tooltip, name === 'toggle' ? ['tooltipshow','tooltiphide'] : ['tooltip'+name], callback, null, tooltip); 1536 }); 1537 1538 // Hide tooltips when leaving current window/frame (but not select/option elements) 1539 if(/mouse(out|leave)/i.test(options.hide.event) && options.hide.leave === 'window') { 1540 this._bind(documentTarget, ['mouseout', 'blur'], function(event) { 1541 if(!/select|option/.test(event.target.nodeName) && !event.relatedTarget) { 1542 this.hide(event); 1543 } 1544 }); 1545 } 1546 1547 // Enable hide.fixed by adding appropriate class 1548 if(options.hide.fixed) { 1549 hideTarget = hideTarget.add( tooltip.addClass(CLASS_FIXED) ); 1550 } 1551 1552 /* 1553 * Make sure hoverIntent functions properly by using mouseleave to clear show timer if 1554 * mouseenter/mouseout is used for show.event, even if it isn't in the users options. 1555 */ 1556 else if(/mouse(over|enter)/i.test(options.show.event)) { 1557 this._bind(hideTarget, 'mouseleave', function() { 1558 clearTimeout(this.timers.show); 1559 }); 1560 } 1561 1562 // Hide tooltip on document mousedown if unfocus events are enabled 1563 if(('' + options.hide.event).indexOf('unfocus') > -1) { 1564 this._bind(containerTarget.closest('html'), ['mousedown', 'touchstart'], function(event) { 1565 var elem = $(event.target), 1566 enabled = this.rendered && !this.tooltip.hasClass(CLASS_DISABLED) && this.tooltip[0].offsetWidth > 0, 1567 isAncestor = elem.parents(SELECTOR).filter(this.tooltip[0]).length > 0; 1568 1569 if(elem[0] !== this.target[0] && elem[0] !== this.tooltip[0] && !isAncestor && 1570 !this.target.has(elem[0]).length && enabled 1571 ) { 1572 this.hide(event); 1573 } 1574 }); 1575 } 1576 1577 // Check if the tooltip hides when inactive 1578 if('number' === typeof options.hide.inactive) { 1579 // Bind inactive method to show target(s) as a custom event 1580 this._bind(showTarget, 'qtip-'+this.id+'-inactive', inactiveMethod, 'inactive'); 1581 1582 // Define events which reset the 'inactive' event handler 1583 this._bind(hideTarget.add(tooltip), QTIP.inactiveEvents, inactiveMethod); 1584 } 1585 1586 // Filter and bind events 1587 this._bindEvents(showEvents, hideEvents, showTarget, hideTarget, showMethod, hideMethod); 1588 1589 // Mouse movement bindings 1590 this._bind(showTarget.add(tooltip), 'mousemove', function(event) { 1591 // Check if the tooltip hides when mouse is moved a certain distance 1592 if('number' === typeof options.hide.distance) { 1593 var origin = this.cache.origin || {}, 1594 limit = this.options.hide.distance, 1595 abs = Math.abs; 1596 1597 // Check if the movement has gone beyond the limit, and hide it if so 1598 if(abs(event.pageX - origin.pageX) >= limit || abs(event.pageY - origin.pageY) >= limit) { 1599 this.hide(event); 1600 } 1601 } 1602 1603 // Cache mousemove coords on show targets 1604 this._storeMouse(event); 1605 }); 1606 1607 // Mouse positioning events 1608 if(posOptions.target === 'mouse') { 1609 // If mouse adjustment is on... 1610 if(posOptions.adjust.mouse) { 1611 // Apply a mouseleave event so we don't get problems with overlapping 1612 if(options.hide.event) { 1613 // Track if we're on the target or not 1614 this._bind(showTarget, ['mouseenter', 'mouseleave'], function(event) { 1615 if(!this.cache) {return FALSE; } 1616 this.cache.onTarget = event.type === 'mouseenter'; 1617 }); 1618 } 1619 1620 // Update tooltip position on mousemove 1621 this._bind(documentTarget, 'mousemove', function(event) { 1622 // Update the tooltip position only if the tooltip is visible and adjustment is enabled 1623 if(this.rendered && this.cache.onTarget && !this.tooltip.hasClass(CLASS_DISABLED) && this.tooltip[0].offsetWidth > 0) { 1624 this.reposition(event); 1625 } 1626 }); 1627 } 1628 } 1629 1630 // Adjust positions of the tooltip on window resize if enabled 1631 if(posOptions.adjust.resize || viewportTarget.length) { 1632 this._bind( $.event.special.resize ? viewportTarget : windowTarget, 'resize', repositionMethod ); 1633 } 1634 1635 // Adjust tooltip position on scroll of the window or viewport element if present 1636 if(posOptions.adjust.scroll) { 1637 this._bind( windowTarget.add(posOptions.container), 'scroll', repositionMethod ); 1638 } 1639}; 1640 1641// Un-assignment method 1642PROTOTYPE._unassignEvents = function() { 1643 var options = this.options, 1644 showTargets = options.show.target, 1645 hideTargets = options.hide.target, 1646 targets = $.grep([ 1647 this.elements.target[0], 1648 this.rendered && this.tooltip[0], 1649 options.position.container[0], 1650 options.position.viewport[0], 1651 options.position.container.closest('html')[0], // unfocus 1652 window, 1653 document 1654 ], function(i) { 1655 return typeof i === 'object'; 1656 }); 1657 1658 // Add show and hide targets if they're valid 1659 if(showTargets && showTargets.toArray) { 1660 targets = targets.concat(showTargets.toArray()); 1661 } 1662 if(hideTargets && hideTargets.toArray) { 1663 targets = targets.concat(hideTargets.toArray()); 1664 } 1665 1666 // Unbind the events 1667 this._unbind(targets) 1668 ._unbind(targets, 'destroy') 1669 ._unbind(targets, 'inactive'); 1670}; 1671 1672// Apply common event handlers using delegate (avoids excessive .bind calls!) 1673$(function() { 1674 delegate(SELECTOR, ['mouseenter', 'mouseleave'], function(event) { 1675 var state = event.type === 'mouseenter', 1676 tooltip = $(event.currentTarget), 1677 target = $(event.relatedTarget || event.target), 1678 options = this.options; 1679 1680 // On mouseenter... 1681 if(state) { 1682 // Focus the tooltip on mouseenter (z-index stacking) 1683 this.focus(event); 1684 1685 // Clear hide timer on tooltip hover to prevent it from closing 1686 tooltip.hasClass(CLASS_FIXED) && !tooltip.hasClass(CLASS_DISABLED) && clearTimeout(this.timers.hide); 1687 } 1688 1689 // On mouseleave... 1690 else { 1691 // When mouse tracking is enabled, hide when we leave the tooltip and not onto the show target (if a hide event is set) 1692 if(options.position.target === 'mouse' && options.position.adjust.mouse && 1693 options.hide.event && options.show.target && !target.closest(options.show.target[0]).length) { 1694 this.hide(event); 1695 } 1696 } 1697 1698 // Add hover class 1699 tooltip.toggleClass(CLASS_HOVER, state); 1700 }); 1701 1702 // Define events which reset the 'inactive' event handler 1703 delegate('['+ATTR_ID+']', INACTIVE_EVENTS, inactiveMethod); 1704}); 1705;// Initialization method 1706function init(elem, id, opts) { 1707 var obj, posOptions, attr, config, title, 1708 1709 // Setup element references 1710 docBody = $(document.body), 1711 1712 // Use document body instead of document element if needed 1713 newTarget = elem[0] === document ? docBody : elem, 1714 1715 // Grab metadata from element if plugin is present 1716 metadata = elem.metadata ? elem.metadata(opts.metadata) : NULL, 1717 1718 // If metadata type if HTML5, grab 'name' from the object instead, or use the regular data object otherwise 1719 metadata5 = opts.metadata.type === 'html5' && metadata ? metadata[opts.metadata.name] : NULL, 1720 1721 // Grab data from metadata.name (or data-qtipopts as fallback) using .data() method, 1722 html5 = elem.data(opts.metadata.name || 'qtipopts'); 1723 1724 // If we don't get an object returned attempt to parse it manualyl without parseJSON 1725 /* eslint-disable no-empty */ 1726 try { html5 = typeof html5 === 'string' ? $.parseJSON(html5) : html5; } 1727 catch(e) {} 1728 /* eslint-enable no-empty */ 1729 1730 // Merge in and sanitize metadata 1731 config = $.extend(TRUE, {}, QTIP.defaults, opts,
vendor: 6,559 bytes, lines 1732-1959
1732 typeof html5 === 'object' ? sanitizeOptions(html5) : NULL, 1733 sanitizeOptions(metadata5 || metadata)); 1734 1735 // Re-grab our positioning options now we've merged our metadata and set id to passed value 1736 posOptions = config.position; 1737 config.id = id; 1738 1739 // Setup missing content if none is detected 1740 if('boolean' === typeof config.content.text) { 1741 attr = elem.attr(config.content.attr); 1742 1743 // Grab from supplied attribute if available 1744 if(config.content.attr !== FALSE && attr) { config.content.text = attr; } 1745 1746 // No valid content was found, abort render 1747 else { return FALSE; } 1748 } 1749 1750 // Setup target options 1751 if(!posOptions.container.length) { posOptions.container = docBody; } 1752 if(posOptions.target === FALSE) { posOptions.target = newTarget; } 1753 if(config.show.target === FALSE) { config.show.target = newTarget; } 1754 if(config.show.solo === TRUE) { config.show.solo = posOptions.container.closest('body'); } 1755 if(config.hide.target === FALSE) { config.hide.target = newTarget; } 1756 if(config.position.viewport === TRUE) { config.position.viewport = posOptions.container; } 1757 1758 // Ensure we only use a single container 1759 posOptions.container = posOptions.container.eq(0); 1760 1761 // Convert position corner values into x and y strings 1762 posOptions.at = new CORNER(posOptions.at, TRUE); 1763 posOptions.my = new CORNER(posOptions.my); 1764 1765 // Destroy previous tooltip if overwrite is enabled, or skip element if not 1766 if(elem.data(NAMESPACE)) { 1767 if(config.overwrite) { 1768 elem.qtip('destroy', true); 1769 } 1770 else if(config.overwrite === FALSE) { 1771 return FALSE; 1772 } 1773 } 1774 1775 // Add has-qtip attribute 1776 elem.attr(ATTR_HAS, id); 1777 1778 // Remove title attribute and store it if present 1779 if(config.suppress && (title = elem.attr('title'))) { 1780 // Final attr call fixes event delegatiom and IE default tooltip showing problem 1781 elem.removeAttr('title').attr(oldtitle, title).attr('title', ''); 1782 } 1783 1784 // Initialize the tooltip and add API reference 1785 obj = new QTip(elem, config, id, !!attr); 1786 elem.data(NAMESPACE, obj); 1787 1788 return obj; 1789} 1790 1791// jQuery $.fn extension method 1792QTIP = $.fn.qtip = function(options, notation, newValue) 1793{ 1794 var command = ('' + options).toLowerCase(), // Parse command 1795 returned = NULL, 1796 args = $.makeArray(arguments).slice(1), 1797 event = args[args.length - 1], 1798 opts = this[0] ? $.data(this[0], NAMESPACE) : NULL; 1799 1800 // Check for API request 1801 if(!arguments.length && opts || command === 'api') { 1802 return opts; 1803 } 1804 1805 // Execute API command if present 1806 else if('string' === typeof options) { 1807 this.each(function() { 1808 var api = $.data(this, NAMESPACE); 1809 if(!api) { return TRUE; } 1810 1811 // Cache the event if possible 1812 if(event && event.timeStamp) { api.cache.event = event; } 1813 1814 // Check for specific API commands 1815 if(notation && (command === 'option' || command === 'options')) { 1816 if(newValue !== undefined || $.isPlainObject(notation)) { 1817 api.set(notation, newValue); 1818 } 1819 else { 1820 returned = api.get(notation); 1821 return FALSE; 1822 } 1823 } 1824 1825 // Execute API command 1826 else if(api[command]) { 1827 api[command].apply(api, args); 1828 } 1829 }); 1830 1831 return returned !== NULL ? returned : this; 1832 } 1833 1834 // No API commands. validate provided options and setup qTips 1835 else if('object' === typeof options || !arguments.length) { 1836 // Sanitize options first 1837 opts = sanitizeOptions($.extend(TRUE, {}, options)); 1838 1839 return this.each(function(i) { 1840 var api, id; 1841 1842 // Find next available ID, or use custom ID if provided 1843 id = $.isArray(opts.id) ? opts.id[i] : opts.id; 1844 id = !id || id === FALSE || id.length < 1 || QTIP.api[id] ? QTIP.nextid++ : id; 1845 1846 // Initialize the qTip and re-grab newly sanitized options 1847 api = init($(this), id, opts); 1848 if(api === FALSE) { return TRUE; } 1849 else { QTIP.api[id] = api; } 1850 1851 // Initialize plugins 1852 $.each(PLUGINS, function() { 1853 if(this.initialize === 'initialize') { this(api); } 1854 }); 1855 1856 // Assign initial pre-render events 1857 api._assignInitialEvents(event); 1858 }); 1859 } 1860}; 1861 1862// Expose class 1863$.qtip = QTip; 1864 1865// Populated in render method 1866QTIP.api = {}; 1867;$.each({ 1868 /* Allow other plugins to successfully retrieve the title of an element with a qTip applied */ 1869 attr: function(attr, val) { 1870 if(this.length) { 1871 var self = this[0], 1872 title = 'title', 1873 api = $.data(self, 'qtip'); 1874 1875 if(attr === title && api && api.options && 'object' === typeof api && 'object' === typeof api.options && api.options.suppress) { 1876 if(arguments.length < 2) { 1877 return $.attr(self, oldtitle); 1878 } 1879 1880 // If qTip is rendered and title was originally used as content, update it 1881 if(api && api.options.content.attr === title && api.cache.attr) { 1882 api.set('content.text', val); 1883 } 1884 1885 // Use the regular attr method to set, then cache the result 1886 return this.attr(oldtitle, val); 1887 } 1888 } 1889 1890 return $.fn['attr'+replaceSuffix].apply(this, arguments); 1891 }, 1892 1893 /* Allow clone to correctly retrieve cached title attributes */ 1894 clone: function(keepData) { 1895 // Clone our element using the real clone method 1896 var elems = $.fn['clone'+replaceSuffix].apply(this, arguments); 1897 1898 // Grab all elements with an oldtitle set, and change it to regular title attribute, if keepData is false 1899 if(!keepData) { 1900 elems.filter('['+oldtitle+']').attr('title', function() { 1901 return $.attr(this, oldtitle); 1902 }) 1903 .removeAttr(oldtitle); 1904 } 1905 1906 return elems; 1907 } 1908}, function(name, func) { 1909 if(!func || $.fn[name+replaceSuffix]) { return TRUE; } 1910 1911 var old = $.fn[name+replaceSuffix] = $.fn[name]; 1912 $.fn[name] = function() { 1913 return func.apply(this, arguments) || old.apply(this, arguments); 1914 }; 1915}); 1916 1917/* Fire off 'removeqtip' handler in $.cleanData if jQuery UI not present (it already does similar). 1918 * This snippet is taken directly from jQuery UI source code found here: 1919 * http://code.jquery.com/ui/jquery-ui-git.js 1920 */ 1921if(!$.ui) { 1922 $['cleanData'+replaceSuffix] = $.cleanData; 1923 $.cleanData = function( elems ) { 1924 for(var i = 0, elem; (elem = $( elems[i] )).length; i++) { 1925 if(elem.attr(ATTR_HAS)) { 1926 /* eslint-disable no-empty */ 1927 try { elem.triggerHandler('removeqtip'); } 1928 catch( e ) {} 1929 /* eslint-enable no-empty */ 1930 } 1931 } 1932 $['cleanData'+replaceSuffix].apply(this, arguments); 1933 }; 1934} 1935;// qTip version 1936QTIP.version = '3.0.3'; 1937 1938// Base ID for all qTips 1939QTIP.nextid = 0; 1940 1941// Inactive events array 1942QTIP.inactiveEvents = INACTIVE_EVENTS; 1943 1944// Base z-index for all qTips 1945QTIP.zindex = 15000; 1946 1947// Define configuration defaults 1948QTIP.defaults = { 1949 prerender: FALSE, 1950 id: FALSE, 1951 overwrite: TRUE, 1952 suppress: TRUE, 1953 content: { 1954 text: TRUE, 1955 attr: 'title', 1956 title: FALSE, 1957 button: FALSE 1958 }, 1959 position: {
vendor: 19,933 bytes, lines 1960-2653
1960 my: 'top left', 1961 at: 'bottom right', 1962 target: FALSE, 1963 container: FALSE, 1964 viewport: FALSE, 1965 adjust: { 1966 x: 0, y: 0, 1967 mouse: TRUE, 1968 scroll: TRUE, 1969 resize: TRUE, 1970 method: 'flipinvert flipinvert' 1971 }, 1972 effect: function(api, pos) { 1973 $(this).animate(pos, { 1974 duration: 200, 1975 queue: FALSE 1976 }); 1977 } 1978 }, 1979 show: { 1980 target: FALSE, 1981 event: 'mouseenter', 1982 effect: TRUE, 1983 delay: 90, 1984 solo: FALSE, 1985 ready: FALSE, 1986 autofocus: FALSE 1987 }, 1988 hide: { 1989 target: FALSE, 1990 event: 'mouseleave', 1991 effect: TRUE, 1992 delay: 0, 1993 fixed: FALSE, 1994 inactive: FALSE, 1995 leave: 'window', 1996 distance: FALSE 1997 }, 1998 style: { 1999 classes: '', 2000 widget: FALSE, 2001 width: FALSE, 2002 height: FALSE, 2003 def: TRUE 2004 }, 2005 events: { 2006 render: NULL, 2007 move: NULL, 2008 show: NULL, 2009 hide: NULL, 2010 toggle: NULL, 2011 visible: NULL, 2012 hidden: NULL, 2013 focus: NULL, 2014 blur: NULL 2015 } 2016}; 2017;var TIP, 2018createVML, 2019SCALE, 2020PIXEL_RATIO, 2021BACKING_STORE_RATIO, 2022 2023// Common CSS strings 2024MARGIN = 'margin', 2025BORDER = 'border', 2026COLOR = 'color', 2027BG_COLOR = 'background-color', 2028TRANSPARENT = 'transparent', 2029IMPORTANT = ' !important', 2030 2031// Check if the browser supports <canvas/> elements 2032HASCANVAS = !!document.createElement('canvas').getContext, 2033 2034// Invalid colour values used in parseColours() 2035INVALID = /rgba?\(0, 0, 0(, 0)?\)|transparent|#123456/i; 2036 2037// Camel-case method, taken from jQuery source 2038// http://code.jquery.com/jquery-1.8.0.js 2039function camel(s) { return s.charAt(0).toUpperCase() + s.slice(1); } 2040 2041/* 2042 * Modified from Modernizr's testPropsAll() 2043 * http://modernizr.com/downloads/modernizr-latest.js 2044 */ 2045var cssProps = {}, cssPrefixes = ['Webkit', 'O', 'Moz', 'ms']; 2046function vendorCss(elem, prop) { 2047 var ucProp = prop.charAt(0).toUpperCase() + prop.slice(1), 2048 props = (prop + ' ' + cssPrefixes.join(ucProp + ' ') + ucProp).split(' '), 2049 cur, val, i = 0; 2050 2051 // If the property has already been mapped... 2052 if(cssProps[prop]) { return elem.css(cssProps[prop]); } 2053 2054 while(cur = props[i++]) { 2055 if((val = elem.css(cur)) !== undefined) { 2056 cssProps[prop] = cur; 2057 return val; 2058 } 2059 } 2060} 2061 2062// Parse a given elements CSS property into an int 2063function intCss(elem, prop) { 2064 return Math.ceil(parseFloat(vendorCss(elem, prop))); 2065} 2066 2067 2068// VML creation (for IE only) 2069if(!HASCANVAS) { 2070 createVML = function(tag, props, style) { 2071 return '<qtipvml:'+tag+' xmlns="urn:schemas-microsoft.com:vml" class="qtip-vml" '+(props||'')+ 2072 ' style="behavior: url(#default#VML); '+(style||'')+ '" />'; 2073 }; 2074} 2075 2076// Canvas only definitions 2077else { 2078 PIXEL_RATIO = window.devicePixelRatio || 1; 2079 BACKING_STORE_RATIO = (function() { 2080 var context = document.createElement('canvas').getContext('2d'); 2081 return context.backingStorePixelRatio || context.webkitBackingStorePixelRatio || context.mozBackingStorePixelRatio || 2082 context.msBackingStorePixelRatio || context.oBackingStorePixelRatio || 1; 2083 })(); 2084 SCALE = PIXEL_RATIO / BACKING_STORE_RATIO; 2085} 2086 2087 2088function Tip(qtip, options) { 2089 this._ns = 'tip'; 2090 this.options = options; 2091 this.offset = options.offset; 2092 this.size = [ options.width, options.height ]; 2093 2094 // Initialize 2095 this.qtip = qtip; 2096 this.init(qtip); 2097} 2098 2099$.extend(Tip.prototype, { 2100 init: function(qtip) { 2101 var context, tip; 2102 2103 // Create tip element and prepend to the tooltip 2104 tip = this.element = qtip.elements.tip = $('<div />', { 'class': NAMESPACE+'-tip' }).prependTo(qtip.tooltip); 2105 2106 // Create tip drawing element(s) 2107 if(HASCANVAS) { 2108 // save() as soon as we create the canvas element so FF2 doesn't bork on our first restore()! 2109 context = $('<canvas />').appendTo(this.element)[0].getContext('2d'); 2110 2111 // Setup constant parameters 2112 context.lineJoin = 'miter'; 2113 context.miterLimit = 100000; 2114 context.save(); 2115 } 2116 else { 2117 context = createVML('shape', 'coordorigin="0,0"', 'position:absolute;'); 2118 this.element.html(context + context); 2119 2120 // Prevent mousing down on the tip since it causes problems with .live() handling in IE due to VML 2121 qtip._bind( $('*', tip).add(tip), ['click', 'mousedown'], function(event) { event.stopPropagation(); }, this._ns); 2122 } 2123 2124 // Bind update events 2125 qtip._bind(qtip.tooltip, 'tooltipmove', this.reposition, this._ns, this); 2126 2127 // Create it 2128 this.create(); 2129 }, 2130 2131 _swapDimensions: function() { 2132 this.size[0] = this.options.height; 2133 this.size[1] = this.options.width; 2134 }, 2135 _resetDimensions: function() { 2136 this.size[0] = this.options.width; 2137 this.size[1] = this.options.height; 2138 }, 2139 2140 _useTitle: function(corner) { 2141 var titlebar = this.qtip.elements.titlebar; 2142 return titlebar && ( 2143 corner.y === TOP || corner.y === CENTER && this.element.position().top + this.size[1] / 2 + this.options.offset < titlebar.outerHeight(TRUE) 2144 ); 2145 }, 2146 2147 _parseCorner: function(corner) { 2148 var my = this.qtip.options.position.my; 2149 2150 // Detect corner and mimic properties 2151 if(corner === FALSE || my === FALSE) { 2152 corner = FALSE; 2153 } 2154 else if(corner === TRUE) { 2155 corner = new CORNER( my.string() ); 2156 } 2157 else if(!corner.string) { 2158 corner = new CORNER(corner); 2159 corner.fixed = TRUE; 2160 } 2161 2162 return corner; 2163 }, 2164 2165 _parseWidth: function(corner, side, use) { 2166 var elements = this.qtip.elements, 2167 prop = BORDER + camel(side) + 'Width'; 2168 2169 return (use ? intCss(use, prop) : 2170 intCss(elements.content, prop) || 2171 intCss(this._useTitle(corner) && elements.titlebar || elements.content, prop) || 2172 intCss(elements.tooltip, prop) 2173 ) || 0; 2174 }, 2175 2176 _parseRadius: function(corner) { 2177 var elements = this.qtip.elements, 2178 prop = BORDER + camel(corner.y) + camel(corner.x) + 'Radius'; 2179 2180 return BROWSER.ie < 9 ? 0 : 2181 intCss(this._useTitle(corner) && elements.titlebar || elements.content, prop) || 2182 intCss(elements.tooltip, prop) || 0; 2183 }, 2184 2185 _invalidColour: function(elem, prop, compare) { 2186 var val = elem.css(prop); 2187 return !val || compare && val === elem.css(compare) || INVALID.test(val) ? FALSE : val; 2188 }, 2189 2190 _parseColours: function(corner) { 2191 var elements = this.qtip.elements, 2192 tip = this.element.css('cssText', ''), 2193 borderSide = BORDER + camel(corner[ corner.precedance ]) + camel(COLOR), 2194 colorElem = this._useTitle(corner) && elements.titlebar || elements.content, 2195 css = this._invalidColour, color = []; 2196 2197 // Attempt to detect the background colour from various elements, left-to-right precedance 2198 color[0] = css(tip, BG_COLOR) || css(colorElem, BG_COLOR) || css(elements.content, BG_COLOR) || 2199 css(elements.tooltip, BG_COLOR) || tip.css(BG_COLOR); 2200 2201 // Attempt to detect the correct border side colour from various elements, left-to-right precedance 2202 color[1] = css(tip, borderSide, COLOR) || css(colorElem, borderSide, COLOR) || 2203 css(elements.content, borderSide, COLOR) || css(elements.tooltip, borderSide, COLOR) || elements.tooltip.css(borderSide); 2204 2205 // Reset background and border colours 2206 $('*', tip).add(tip).css('cssText', BG_COLOR+':'+TRANSPARENT+IMPORTANT+';'+BORDER+':0'+IMPORTANT+';'); 2207 2208 return color; 2209 }, 2210 2211 _calculateSize: function(corner) { 2212 var y = corner.precedance === Y, 2213 width = this.options.width, 2214 height = this.options.height, 2215 isCenter = corner.abbrev() === 'c', 2216 base = (y ? width: height) * (isCenter ? 0.5 : 1), 2217 pow = Math.pow, 2218 round = Math.round, 2219 bigHyp, ratio, result, 2220 2221 smallHyp = Math.sqrt( pow(base, 2) + pow(height, 2) ), 2222 hyp = [ 2223 this.border / base * smallHyp, 2224 this.border / height * smallHyp 2225 ]; 2226 2227 hyp[2] = Math.sqrt( pow(hyp[0], 2) - pow(this.border, 2) ); 2228 hyp[3] = Math.sqrt( pow(hyp[1], 2) - pow(this.border, 2) ); 2229 2230 bigHyp = smallHyp + hyp[2] + hyp[3] + (isCenter ? 0 : hyp[0]); 2231 ratio = bigHyp / smallHyp; 2232 2233 result = [ round(ratio * width), round(ratio * height) ]; 2234 return y ? result : result.reverse(); 2235 }, 2236 2237 // Tip coordinates calculator 2238 _calculateTip: function(corner, size, scale) { 2239 scale = scale || 1; 2240 size = size || this.size; 2241 2242 var width = size[0] * scale, 2243 height = size[1] * scale, 2244 width2 = Math.ceil(width / 2), height2 = Math.ceil(height / 2), 2245 2246 // Define tip coordinates in terms of height and width values 2247 tips = { 2248 br: [0,0, width,height, width,0], 2249 bl: [0,0, width,0, 0,height], 2250 tr: [0,height, width,0, width,height], 2251 tl: [0,0, 0,height, width,height], 2252 tc: [0,height, width2,0, width,height], 2253 bc: [0,0, width,0, width2,height], 2254 rc: [0,0, width,height2, 0,height], 2255 lc: [width,0, width,height, 0,height2] 2256 }; 2257 2258 // Set common side shapes 2259 tips.lt = tips.br; tips.rt = tips.bl; 2260 tips.lb = tips.tr; tips.rb = tips.tl; 2261 2262 return tips[ corner.abbrev() ]; 2263 }, 2264 2265 // Tip coordinates drawer (canvas) 2266 _drawCoords: function(context, coords) { 2267 context.beginPath(); 2268 context.moveTo(coords[0], coords[1]); 2269 context.lineTo(coords[2], coords[3]); 2270 context.lineTo(coords[4], coords[5]); 2271 context.closePath(); 2272 }, 2273 2274 create: function() { 2275 // Determine tip corner 2276 var c = this.corner = (HASCANVAS || BROWSER.ie) && this._parseCorner(this.options.corner); 2277 2278 // If we have a tip corner... 2279 this.enabled = !!this.corner && this.corner.abbrev() !== 'c'; 2280 if(this.enabled) { 2281 // Cache it 2282 this.qtip.cache.corner = c.clone(); 2283 2284 // Create it 2285 this.update(); 2286 } 2287 2288 // Toggle tip element 2289 this.element.toggle(this.enabled); 2290 2291 return this.corner; 2292 }, 2293 2294 update: function(corner, position) { 2295 if(!this.enabled) { return this; } 2296 2297 var elements = this.qtip.elements, 2298 tip = this.element, 2299 inner = tip.children(), 2300 options = this.options, 2301 curSize = this.size, 2302 mimic = options.mimic, 2303 round = Math.round, 2304 color, precedance, context, 2305 coords, bigCoords, translate, newSize, border; 2306 2307 // Re-determine tip if not already set 2308 if(!corner) { corner = this.qtip.cache.corner || this.corner; } 2309 2310 // Use corner property if we detect an invalid mimic value 2311 if(mimic === FALSE) { mimic = corner; } 2312 2313 // Otherwise inherit mimic properties from the corner object as necessary 2314 else { 2315 mimic = new CORNER(mimic); 2316 mimic.precedance = corner.precedance; 2317 2318 if(mimic.x === 'inherit') { mimic.x = corner.x; } 2319 else if(mimic.y === 'inherit') { mimic.y = corner.y; } 2320 else if(mimic.x === mimic.y) { 2321 mimic[ corner.precedance ] = corner[ corner.precedance ]; 2322 } 2323 } 2324 precedance = mimic.precedance; 2325 2326 // Ensure the tip width.height are relative to the tip position 2327 if(corner.precedance === X) { this._swapDimensions(); } 2328 else { this._resetDimensions(); } 2329 2330 // Update our colours 2331 color = this.color = this._parseColours(corner); 2332 2333 // Detect border width, taking into account colours 2334 if(color[1] !== TRANSPARENT) { 2335 // Grab border width 2336 border = this.border = this._parseWidth(corner, corner[corner.precedance]); 2337 2338 // If border width isn't zero, use border color as fill if it's not invalid (1.0 style tips) 2339 if(options.border && border < 1 && !INVALID.test(color[1])) { color[0] = color[1]; } 2340 2341 // Set border width (use detected border width if options.border is true) 2342 this.border = border = options.border !== TRUE ? options.border : border; 2343 } 2344 2345 // Border colour was invalid, set border to zero 2346 else { this.border = border = 0; } 2347 2348 // Determine tip size 2349 newSize = this.size = this._calculateSize(corner); 2350 tip.css({ 2351 width: newSize[0], 2352 height: newSize[1], 2353 lineHeight: newSize[1]+'px' 2354 }); 2355 2356 // Calculate tip translation 2357 if(corner.precedance === Y) { 2358 translate = [ 2359 round(mimic.x === LEFT ? border : mimic.x === RIGHT ? newSize[0] - curSize[0] - border : (newSize[0] - curSize[0]) / 2), 2360 round(mimic.y === TOP ? newSize[1] - curSize[1] : 0) 2361 ]; 2362 } 2363 else { 2364 translate = [ 2365 round(mimic.x === LEFT ? newSize[0] - curSize[0] : 0), 2366 round(mimic.y === TOP ? border : mimic.y === BOTTOM ? newSize[1] - curSize[1] - border : (newSize[1] - curSize[1]) / 2) 2367 ]; 2368 } 2369 2370 // Canvas drawing implementation 2371 if(HASCANVAS) { 2372 // Grab canvas context and clear/save it 2373 context = inner[0].getContext('2d'); 2374 context.restore(); context.save(); 2375 context.clearRect(0,0,6000,6000); 2376 2377 // Calculate coordinates 2378 coords = this._calculateTip(mimic, curSize, SCALE); 2379 bigCoords = this._calculateTip(mimic, this.size, SCALE); 2380 2381 // Set the canvas size using calculated size 2382 inner.attr(WIDTH, newSize[0] * SCALE).attr(HEIGHT, newSize[1] * SCALE); 2383 inner.css(WIDTH, newSize[0]).css(HEIGHT, newSize[1]); 2384 2385 // Draw the outer-stroke tip 2386 this._drawCoords(context, bigCoords); 2387 context.fillStyle = color[1]; 2388 context.fill(); 2389 2390 // Draw the actual tip 2391 context.translate(translate[0] * SCALE, translate[1] * SCALE); 2392 this._drawCoords(context, coords); 2393 context.fillStyle = color[0]; 2394 context.fill(); 2395 } 2396 2397 // VML (IE Proprietary implementation) 2398 else { 2399 // Calculate coordinates 2400 coords = this._calculateTip(mimic); 2401 2402 // Setup coordinates string 2403 coords = 'm' + coords[0] + ',' + coords[1] + ' l' + coords[2] + 2404 ',' + coords[3] + ' ' + coords[4] + ',' + coords[5] + ' xe'; 2405 2406 // Setup VML-specific offset for pixel-perfection 2407 translate[2] = border && /^(r|b)/i.test(corner.string()) ? 2408 BROWSER.ie === 8 ? 2 : 1 : 0; 2409 2410 // Set initial CSS 2411 inner.css({ 2412 coordsize: newSize[0]+border + ' ' + newSize[1]+border, 2413 antialias: ''+(mimic.string().indexOf(CENTER) > -1), 2414 left: translate[0] - translate[2] * Number(precedance === X), 2415 top: translate[1] - translate[2] * Number(precedance === Y), 2416 width: newSize[0] + border, 2417 height: newSize[1] + border 2418 }) 2419 .each(function(i) { 2420 var $this = $(this); 2421 2422 // Set shape specific attributes 2423 $this[ $this.prop ? 'prop' : 'attr' ]({ 2424 coordsize: newSize[0]+border + ' ' + newSize[1]+border, 2425 path: coords, 2426 fillcolor: color[0], 2427 filled: !!i, 2428 stroked: !i 2429 }) 2430 .toggle(!!(border || i)); 2431 2432 // Check if border is enabled and add stroke element 2433 !i && $this.html( createVML( 2434 'stroke', 'weight="'+border*2+'px" color="'+color[1]+'" miterlimit="1000" joinstyle="miter"' 2435 ) ); 2436 }); 2437 } 2438 2439 // Opera bug #357 - Incorrect tip position 2440 // https://github.com/Craga89/qTip2/issues/367 2441 window.opera && setTimeout(function() { 2442 elements.tip.css({ 2443 display: 'inline-block', 2444 visibility: 'visible' 2445 }); 2446 }, 1); 2447 2448 // Position if needed 2449 if(position !== FALSE) { this.calculate(corner, newSize); } 2450 }, 2451 2452 calculate: function(corner, size) { 2453 if(!this.enabled) { return FALSE; } 2454 2455 var self = this, 2456 elements = this.qtip.elements, 2457 tip = this.element, 2458 userOffset = this.options.offset, 2459 position = {}, 2460 precedance, corners; 2461 2462 // Inherit corner if not provided 2463 corner = corner || this.corner; 2464 precedance = corner.precedance; 2465 2466 // Determine which tip dimension to use for adjustment 2467 size = size || this._calculateSize(corner); 2468 2469 // Setup corners and offset array 2470 corners = [ corner.x, corner.y ]; 2471 if(precedance === X) { corners.reverse(); } 2472 2473 // Calculate tip position 2474 $.each(corners, function(i, side) { 2475 var b, bc, br; 2476 2477 if(side === CENTER) { 2478 b = precedance === Y ? LEFT : TOP; 2479 position[ b ] = '50%'; 2480 position[MARGIN+'-' + b] = -Math.round(size[ precedance === Y ? 0 : 1 ] / 2) + userOffset; 2481 } 2482 else { 2483 b = self._parseWidth(corner, side, elements.tooltip); 2484 bc = self._parseWidth(corner, side, elements.content); 2485 br = self._parseRadius(corner); 2486 2487 position[ side ] = Math.max(-self.border, i ? bc : userOffset + (br > b ? br : -b)); 2488 } 2489 }); 2490 2491 // Adjust for tip size 2492 position[ corner[precedance] ] -= size[ precedance === X ? 0 : 1 ]; 2493 2494 // Set and return new position 2495 tip.css({ margin: '', top: '', bottom: '', left: '', right: '' }).css(position); 2496 return position; 2497 }, 2498 2499 reposition: function(event, api, pos) { 2500 if(!this.enabled) { return; } 2501 2502 var cache = api.cache, 2503 newCorner = this.corner.clone(), 2504 adjust = pos.adjusted, 2505 method = api.options.position.adjust.method.split(' '), 2506 horizontal = method[0], 2507 vertical = method[1] || method[0], 2508 shift = { left: FALSE, top: FALSE, x: 0, y: 0 }, 2509 offset, css = {}, props; 2510 2511 function shiftflip(direction, precedance, popposite, side, opposite) { 2512 // Horizontal - Shift or flip method 2513 if(direction === SHIFT && newCorner.precedance === precedance && adjust[side] && newCorner[popposite] !== CENTER) { 2514 newCorner.precedance = newCorner.precedance === X ? Y : X; 2515 } 2516 else if(direction !== SHIFT && adjust[side]){ 2517 newCorner[precedance] = newCorner[precedance] === CENTER ? 2518 adjust[side] > 0 ? side : opposite : 2519 newCorner[precedance] === side ? opposite : side; 2520 } 2521 } 2522 2523 function shiftonly(xy, side, opposite) { 2524 if(newCorner[xy] === CENTER) { 2525 css[MARGIN+'-'+side] = shift[xy] = offset[MARGIN+'-'+side] - adjust[side]; 2526 } 2527 else { 2528 props = offset[opposite] !== undefined ? 2529 [ adjust[side], -offset[side] ] : [ -adjust[side], offset[side] ]; 2530 2531 if( (shift[xy] = Math.max(props[0], props[1])) > props[0] ) { 2532 pos[side] -= adjust[side]; 2533 shift[side] = FALSE; 2534 } 2535 2536 css[ offset[opposite] !== undefined ? opposite : side ] = shift[xy]; 2537 } 2538 } 2539 2540 // If our tip position isn't fixed e.g. doesn't adjust with viewport... 2541 if(this.corner.fixed !== TRUE) { 2542 // Perform shift/flip adjustments 2543 shiftflip(horizontal, X, Y, LEFT, RIGHT); 2544 shiftflip(vertical, Y, X, TOP, BOTTOM); 2545 2546 // Update and redraw the tip if needed (check cached details of last drawn tip) 2547 if(newCorner.string() !== cache.corner.string() || cache.cornerTop !== adjust.top || cache.cornerLeft !== adjust.left) { 2548 this.update(newCorner, FALSE); 2549 } 2550 } 2551 2552 // Setup tip offset properties 2553 offset = this.calculate(newCorner); 2554 2555 // Readjust offset object to make it left/top 2556 if(offset.right !== undefined) { offset.left = -offset.right; } 2557 if(offset.bottom !== undefined) { offset.top = -offset.bottom; } 2558 offset.user = this.offset; 2559 2560 // Perform shift adjustments 2561 shift.left = horizontal === SHIFT && !!adjust.left; 2562 if(shift.left) { 2563 shiftonly(X, LEFT, RIGHT); 2564 } 2565 shift.top = vertical === SHIFT && !!adjust.top; 2566 if(shift.top) { 2567 shiftonly(Y, TOP, BOTTOM); 2568 } 2569 2570 /* 2571 * If the tip is adjusted in both dimensions, or in a 2572 * direction that would cause it to be anywhere but the 2573 * outer border, hide it! 2574 */ 2575 this.element.css(css).toggle( 2576 !(shift.x && shift.y || newCorner.x === CENTER && shift.y || newCorner.y === CENTER && shift.x) 2577 ); 2578 2579 // Adjust position to accomodate tip dimensions 2580 pos.left -= offset.left.charAt ? offset.user : 2581 horizontal !== SHIFT || shift.top || !shift.left && !shift.top ? offset.left + this.border : 0; 2582 pos.top -= offset.top.charAt ? offset.user : 2583 vertical !== SHIFT || shift.left || !shift.left && !shift.top ? offset.top + this.border : 0; 2584 2585 // Cache details 2586 cache.cornerLeft = adjust.left; cache.cornerTop = adjust.top; 2587 cache.corner = newCorner.clone(); 2588 }, 2589 2590 destroy: function() { 2591 // Unbind events 2592 this.qtip._unbind(this.qtip.tooltip, this._ns); 2593 2594 // Remove the tip element(s) 2595 if(this.qtip.elements.tip) { 2596 this.qtip.elements.tip.find('*') 2597 .remove().end().remove(); 2598 } 2599 } 2600}); 2601 2602TIP = PLUGINS.tip = function(api) { 2603 return new Tip(api, api.options.style.tip); 2604}; 2605 2606// Initialize tip on render 2607TIP.initialize = 'render'; 2608 2609// Setup plugin sanitization options 2610TIP.sanitize = function(options) { 2611 if(options.style && 'tip' in options.style) { 2612 var opts = options.style.tip; 2613 if(typeof opts !== 'object') { opts = options.style.tip = { corner: opts }; } 2614 if(!(/string|boolean/i).test(typeof opts.corner)) { opts.corner = TRUE; } 2615 } 2616}; 2617 2618// Add new option checks for the plugin 2619CHECKS.tip = { 2620 '^position.my|style.tip.(corner|mimic|border)$': function() { 2621 // Make sure a tip can be drawn 2622 this.create(); 2623 2624 // Reposition the tooltip 2625 this.qtip.reposition(); 2626 }, 2627 '^style.tip.(height|width)$': function(obj) { 2628 // Re-set dimensions and redraw the tip 2629 this.size = [ obj.width, obj.height ]; 2630 this.update(); 2631 2632 // Reposition the tooltip 2633 this.qtip.reposition(); 2634 }, 2635 '^content.title|style.(classes|widget)$': function() { 2636 this.update(); 2637 } 2638}; 2639 2640// Extend original qTip defaults 2641$.extend(TRUE, QTIP.defaults, { 2642 style: { 2643 tip: { 2644 corner: TRUE, 2645 mimic: FALSE, 2646 width: 6, 2647 height: 6, 2648 border: TRUE, 2649 offset: 0 2650 } 2651 } 2652}); 2653;
vendor: 11,490 bytes, lines 2653-3009
2653PLUGINS.viewport = function(api, position, posOptions, targetWidth, targetHeight, elemWidth, elemHeight) 2654{ 2655 var target = posOptions.target, 2656 tooltip = api.elements.tooltip, 2657 my = posOptions.my, 2658 at = posOptions.at, 2659 adjust = posOptions.adjust, 2660 method = adjust.method.split(' '), 2661 methodX = method[0], 2662 methodY = method[1] || method[0], 2663 viewport = posOptions.viewport, 2664 container = posOptions.container, 2665 adjusted = { left: 0, top: 0 }, 2666 fixed, newMy, containerOffset, containerStatic, 2667 viewportWidth, viewportHeight, viewportScroll, viewportOffset; 2668 2669 // If viewport is not a jQuery element, or it's the window/document, or no adjustment method is used... return 2670 if(!viewport.jquery || target[0] === window || target[0] === document.body || adjust.method === 'none') { 2671 return adjusted; 2672 } 2673 2674 // Cach container details 2675 containerOffset = container.offset() || adjusted; 2676 containerStatic = container.css('position') === 'static'; 2677 2678 // Cache our viewport details 2679 fixed = tooltip.css('position') === 'fixed'; 2680 viewportWidth = viewport[0] === window ? viewport.width() : viewport.outerWidth(FALSE); 2681 viewportHeight = viewport[0] === window ? viewport.height() : viewport.outerHeight(FALSE); 2682 viewportScroll = { left: fixed ? 0 : viewport.scrollLeft(), top: fixed ? 0 : viewport.scrollTop() }; 2683 viewportOffset = viewport.offset() || adjusted; 2684 2685 // Generic calculation method 2686 function calculate(side, otherSide, type, adjustment, side1, side2, lengthName, targetLength, elemLength) { 2687 var initialPos = position[side1], 2688 mySide = my[side], 2689 atSide = at[side], 2690 isShift = type === SHIFT, 2691 myLength = mySide === side1 ? elemLength : mySide === side2 ? -elemLength : -elemLength / 2, 2692 atLength = atSide === side1 ? targetLength : atSide === side2 ? -targetLength : -targetLength / 2, 2693 sideOffset = viewportScroll[side1] + viewportOffset[side1] - (containerStatic ? 0 : containerOffset[side1]), 2694 overflow1 = sideOffset - initialPos, 2695 overflow2 = initialPos + elemLength - (lengthName === WIDTH ? viewportWidth : viewportHeight) - sideOffset, 2696 offset = myLength - (my.precedance === side || mySide === my[otherSide] ? atLength : 0) - (atSide === CENTER ? targetLength / 2 : 0); 2697 2698 // shift 2699 if(isShift) { 2700 offset = (mySide === side1 ? 1 : -1) * myLength; 2701 2702 // Adjust position but keep it within viewport dimensions 2703 position[side1] += overflow1 > 0 ? overflow1 : overflow2 > 0 ? -overflow2 : 0; 2704 position[side1] = Math.max( 2705 -containerOffset[side1] + viewportOffset[side1], 2706 initialPos - offset, 2707 Math.min( 2708 Math.max( 2709 -containerOffset[side1] + viewportOffset[side1] + (lengthName === WIDTH ? viewportWidth : viewportHeight), 2710 initialPos + offset 2711 ), 2712 position[side1], 2713 2714 // Make sure we don't adjust complete off the element when using 'center' 2715 mySide === 'center' ? initialPos - myLength : 1E9 2716 ) 2717 ); 2718 2719 } 2720 2721 // flip/flipinvert 2722 else { 2723 // Update adjustment amount depending on if using flipinvert or flip 2724 adjustment *= type === FLIPINVERT ? 2 : 0; 2725 2726 // Check for overflow on the left/top 2727 if(overflow1 > 0 && (mySide !== side1 || overflow2 > 0)) { 2728 position[side1] -= offset + adjustment; 2729 newMy.invert(side, side1); 2730 } 2731 2732 // Check for overflow on the bottom/right 2733 else if(overflow2 > 0 && (mySide !== side2 || overflow1 > 0) ) { 2734 position[side1] -= (mySide === CENTER ? -offset : offset) + adjustment; 2735 newMy.invert(side, side2); 2736 } 2737 2738 // Make sure we haven't made things worse with the adjustment and reset if so 2739 if(position[side1] < viewportScroll[side1] && -position[side1] > overflow2) { 2740 position[side1] = initialPos; newMy = my.clone(); 2741 } 2742 } 2743 2744 return position[side1] - initialPos; 2745 } 2746 2747 // Set newMy if using flip or flipinvert methods 2748 if(methodX !== 'shift' || methodY !== 'shift') { newMy = my.clone(); } 2749 2750 // Adjust position based onviewport and adjustment options 2751 adjusted = { 2752 left: methodX !== 'none' ? calculate( X, Y, methodX, adjust.x, LEFT, RIGHT, WIDTH, targetWidth, elemWidth ) : 0, 2753 top: methodY !== 'none' ? calculate( Y, X, methodY, adjust.y, TOP, BOTTOM, HEIGHT, targetHeight, elemHeight ) : 0, 2754 my: newMy 2755 }; 2756 2757 return adjusted; 2758}; 2759;PLUGINS.polys = { 2760 // POLY area coordinate calculator 2761 // Special thanks to Ed Cradock for helping out with this. 2762 // Uses a binary search algorithm to find suitable coordinates. 2763 polygon: function(baseCoords, corner) { 2764 var result = { 2765 width: 0, height: 0, 2766 position: { 2767 top: 1e10, right: 0, 2768 bottom: 0, left: 1e10 2769 }, 2770 adjustable: FALSE 2771 }, 2772 i = 0, next, 2773 coords = [], 2774 compareX = 1, compareY = 1, 2775 realX = 0, realY = 0, 2776 newWidth, newHeight; 2777 2778 // First pass, sanitize coords and determine outer edges 2779 i = baseCoords.length; 2780 while(i--) { 2781 next = [ parseInt(baseCoords[--i], 10), parseInt(baseCoords[i+1], 10) ]; 2782 2783 if(next[0] > result.position.right){ result.position.right = next[0]; } 2784 if(next[0] < result.position.left){ result.position.left = next[0]; } 2785 if(next[1] > result.position.bottom){ result.position.bottom = next[1]; } 2786 if(next[1] < result.position.top){ result.position.top = next[1]; } 2787 2788 coords.push(next); 2789 } 2790 2791 // Calculate height and width from outer edges 2792 newWidth = result.width = Math.abs(result.position.right - result.position.left); 2793 newHeight = result.height = Math.abs(result.position.bottom - result.position.top); 2794 2795 // If it's the center corner... 2796 if(corner.abbrev() === 'c') { 2797 result.position = { 2798 left: result.position.left + result.width / 2, 2799 top: result.position.top + result.height / 2 2800 }; 2801 } 2802 else { 2803 // Second pass, use a binary search algorithm to locate most suitable coordinate 2804 while(newWidth > 0 && newHeight > 0 && compareX > 0 && compareY > 0) 2805 { 2806 newWidth = Math.floor(newWidth / 2); 2807 newHeight = Math.floor(newHeight / 2); 2808 2809 if(corner.x === LEFT){ compareX = newWidth; } 2810 else if(corner.x === RIGHT){ compareX = result.width - newWidth; } 2811 else{ compareX += Math.floor(newWidth / 2); } 2812 2813 if(corner.y === TOP){ compareY = newHeight; } 2814 else if(corner.y === BOTTOM){ compareY = result.height - newHeight; } 2815 else{ compareY += Math.floor(newHeight / 2); } 2816 2817 i = coords.length; 2818 while(i--) 2819 { 2820 if(coords.length < 2){ break; } 2821 2822 realX = coords[i][0] - result.position.left; 2823 realY = coords[i][1] - result.position.top; 2824 2825 if( 2826 corner.x === LEFT && realX >= compareX || 2827 corner.x === RIGHT && realX <= compareX || 2828 corner.x === CENTER && (realX < compareX || realX > result.width - compareX) || 2829 corner.y === TOP && realY >= compareY || 2830 corner.y === BOTTOM && realY <= compareY || 2831 corner.y === CENTER && (realY < compareY || realY > result.height - compareY)) { 2832 coords.splice(i, 1); 2833 } 2834 } 2835 } 2836 result.position = { left: coords[0][0], top: coords[0][1] }; 2837 } 2838 2839 return result; 2840 }, 2841 2842 rect: function(ax, ay, bx, by) { 2843 return { 2844 width: Math.abs(bx - ax), 2845 height: Math.abs(by - ay), 2846 position: { 2847 left: Math.min(ax, bx), 2848 top: Math.min(ay, by) 2849 } 2850 }; 2851 }, 2852 2853 _angles: { 2854 tc: 3 / 2, tr: 7 / 4, tl: 5 / 4, 2855 bc: 1 / 2, br: 1 / 4, bl: 3 / 4, 2856 rc: 2, lc: 1, c: 0 2857 }, 2858 ellipse: function(cx, cy, rx, ry, corner) { 2859 var c = PLUGINS.polys._angles[ corner.abbrev() ], 2860 rxc = c === 0 ? 0 : rx * Math.cos( c * Math.PI ), 2861 rys = ry * Math.sin( c * Math.PI ); 2862 2863 return { 2864 width: rx * 2 - Math.abs(rxc), 2865 height: ry * 2 - Math.abs(rys), 2866 position: { 2867 left: cx + rxc, 2868 top: cy + rys 2869 }, 2870 adjustable: FALSE 2871 }; 2872 }, 2873 circle: function(cx, cy, r, corner) { 2874 return PLUGINS.polys.ellipse(cx, cy, r, r, corner); 2875 } 2876}; 2877;PLUGINS.imagemap = function(api, area, corner) 2878{ 2879 if(!area.jquery) { area = $(area); } 2880 2881 var shape = (area.attr('shape') || 'rect').toLowerCase().replace('poly', 'polygon'), 2882 image = $('img[usemap="#'+area.parent('map').attr('name')+'"]'), 2883 coordsString = $.trim(area.attr('coords')), 2884 coordsArray = coordsString.replace(/,$/, '').split(','), 2885 imageOffset, coords, i, result, len; 2886 2887 // If we can't find the image using the map... 2888 if(!image.length) { return FALSE; } 2889 2890 // Pass coordinates string if polygon 2891 if(shape === 'polygon') { 2892 result = PLUGINS.polys.polygon(coordsArray, corner); 2893 } 2894 2895 // Otherwise parse the coordinates and pass them as arguments 2896 else if(PLUGINS.polys[shape]) { 2897 for(i = -1, len = coordsArray.length, coords = []; ++i < len;) { 2898 coords.push( parseInt(coordsArray[i], 10) ); 2899 } 2900 2901 result = PLUGINS.polys[shape].apply( 2902 this, coords.concat(corner) 2903 ); 2904 } 2905 2906 // If no shapre calculation method was found, return false 2907 else { return FALSE; } 2908 2909 // Make sure we account for padding and borders on the image 2910 imageOffset = image.offset(); 2911 imageOffset.left += Math.ceil((image.outerWidth(FALSE) - image.width()) / 2); 2912 imageOffset.top += Math.ceil((image.outerHeight(FALSE) - image.height()) / 2); 2913 2914 // Add image position to offset coordinates 2915 result.position.left += imageOffset.left; 2916 result.position.top += imageOffset.top; 2917 2918 return result; 2919}; 2920;PLUGINS.svg = function(api, svg, corner) 2921{ 2922 var elem = svg[0], 2923 root = $(elem.ownerSVGElement), 2924 ownerDocument = elem.ownerDocument, 2925 strokeWidth2 = (parseInt(svg.css('stroke-width'), 10) || 0) / 2, 2926 frameOffset, mtx, transformed, 2927 len, next, i, points, 2928 result, position; 2929 2930 // Ascend the parentNode chain until we find an element with getBBox() 2931 while(!elem.getBBox) { elem = elem.parentNode; } 2932 if(!elem.getBBox || !elem.parentNode) { return FALSE; } 2933 2934 // Determine which shape calculation to use 2935 switch(elem.nodeName) { 2936 case 'ellipse': 2937 case 'circle': 2938 result = PLUGINS.polys.ellipse( 2939 elem.cx.baseVal.value, 2940 elem.cy.baseVal.value, 2941 (elem.rx || elem.r).baseVal.value + strokeWidth2, 2942 (elem.ry || elem.r).baseVal.value + strokeWidth2, 2943 corner 2944 ); 2945 break; 2946 2947 case 'line': 2948 case 'polygon': 2949 case 'polyline': 2950 // Determine points object (line has none, so mimic using array) 2951 points = elem.points || [ 2952 { x: elem.x1.baseVal.value, y: elem.y1.baseVal.value }, 2953 { x: elem.x2.baseVal.value, y: elem.y2.baseVal.value } 2954 ]; 2955 2956 for(result = [], i = -1, len = points.numberOfItems || points.length; ++i < len;) { 2957 next = points.getItem ? points.getItem(i) : points[i]; 2958 result.push.apply(result, [next.x, next.y]); 2959 } 2960 2961 result = PLUGINS.polys.polygon(result, corner); 2962 break; 2963 2964 // Unknown shape or rectangle? Use bounding box 2965 default: 2966 result = elem.getBBox(); 2967 result = { 2968 width: result.width, 2969 height: result.height, 2970 position: { 2971 left: result.x, 2972 top: result.y 2973 } 2974 }; 2975 break; 2976 } 2977 2978 // Shortcut assignments 2979 position = result.position; 2980 root = root[0]; 2981 2982 // Convert position into a pixel value 2983 if(root.createSVGPoint) { 2984 mtx = elem.getScreenCTM(); 2985 points = root.createSVGPoint(); 2986 2987 points.x = position.left; 2988 points.y = position.top; 2989 transformed = points.matrixTransform( mtx ); 2990 position.left = transformed.x; 2991 position.top = transformed.y; 2992 } 2993 2994 // Check the element is not in a child document, and if so, adjust for frame elements offset 2995 if(ownerDocument !== document && api.position.target !== 'mouse') { 2996 frameOffset = $((ownerDocument.defaultView || ownerDocument.parentWindow).frameElement).offset(); 2997 if(frameOffset) { 2998 position.left += frameOffset.left; 2999 position.top += frameOffset.top; 3000 } 3001 } 3002 3003 // Adjust by scroll offset of owner document 3004 ownerDocument = $(ownerDocument); 3005 position.left += ownerDocument.scrollLeft(); 3006 position.top += ownerDocument.scrollTop(); 3007 3008 return result; 3009}
vendor: 9,204 bytes, lines 3009-3348
3009; 3010;var MODAL, OVERLAY, 3011 MODALCLASS = 'qtip-modal', 3012 MODALSELECTOR = '.'+MODALCLASS; 3013 3014OVERLAY = function() 3015{ 3016 var self = this, 3017 focusableElems = {}, 3018 current, 3019 prevState, 3020 elem; 3021 3022 // Modified code from jQuery UI 1.10.0 source 3023 // http://code.jquery.com/ui/1.10.0/jquery-ui.js 3024 function focusable(element) { 3025 // Use the defined focusable checker when possible 3026 if($.expr[':'].focusable) { return $.expr[':'].focusable; } 3027 3028 var isTabIndexNotNaN = !isNaN($.attr(element, 'tabindex')), 3029 nodeName = element.nodeName && element.nodeName.toLowerCase(), 3030 map, mapName, img; 3031 3032 if('area' === nodeName) { 3033 map = element.parentNode; 3034 mapName = map.name; 3035 if(!element.href || !mapName || map.nodeName.toLowerCase() !== 'map') { 3036 return false; 3037 } 3038 img = $('img[usemap=#' + mapName + ']')[0]; 3039 return !!img && img.is(':visible'); 3040 } 3041 3042 return /input|select|textarea|button|object/.test( nodeName ) ? 3043 !element.disabled : 3044 'a' === nodeName ? 3045 element.href || isTabIndexNotNaN : 3046 isTabIndexNotNaN 3047 ; 3048 } 3049 3050 // Focus inputs using cached focusable elements (see update()) 3051 function focusInputs(blurElems) { 3052 // Blurring body element in IE causes window.open windows to unfocus! 3053 if(focusableElems.length < 1 && blurElems.length) { blurElems.not('body').blur(); } 3054 3055 // Focus the inputs 3056 else { focusableElems.first().focus(); } 3057 } 3058 3059 // Steal focus from elements outside tooltip 3060 function stealFocus(event) { 3061 if(!elem.is(':visible')) { return; } 3062 3063 var target = $(event.target), 3064 tooltip = current.tooltip, 3065 container = target.closest(SELECTOR), 3066 targetOnTop; 3067 3068 // Determine if input container target is above this 3069 targetOnTop = container.length < 1 ? FALSE : 3070 parseInt(container[0].style.zIndex, 10) > parseInt(tooltip[0].style.zIndex, 10); 3071 3072 // If we're showing a modal, but focus has landed on an input below 3073 // this modal, divert focus to the first visible input in this modal 3074 // or if we can't find one... the tooltip itself 3075 if(!targetOnTop && target.closest(SELECTOR)[0] !== tooltip[0]) { 3076 focusInputs(target); 3077 } 3078 } 3079 3080 $.extend(self, { 3081 init: function() { 3082 // Create document overlay 3083 elem = self.elem = $('<div />', { 3084 id: 'qtip-overlay', 3085 html: '<div></div>', 3086 mousedown: function() { return FALSE; } 3087 }) 3088 .hide(); 3089 3090 // Make sure we can't focus anything outside the tooltip 3091 $(document.body).bind('focusin'+MODALSELECTOR, stealFocus); 3092 3093 // Apply keyboard "Escape key" close handler 3094 $(document).bind('keydown'+MODALSELECTOR, function(event) { 3095 if(current && current.options.show.modal.escape && event.keyCode === 27) { 3096 current.hide(event); 3097 } 3098 }); 3099 3100 // Apply click handler for blur option 3101 elem.bind('click'+MODALSELECTOR, function(event) { 3102 if(current && current.options.show.modal.blur) { 3103 current.hide(event); 3104 } 3105 }); 3106 3107 return self; 3108 }, 3109 3110 update: function(api) { 3111 // Update current API reference 3112 current = api; 3113 3114 // Update focusable elements if enabled 3115 if(api.options.show.modal.stealfocus !== FALSE) { 3116 focusableElems = api.tooltip.find('*').filter(function() { 3117 return focusable(this); 3118 }); 3119 } 3120 else { focusableElems = []; } 3121 }, 3122 3123 toggle: function(api, state, duration) { 3124 var tooltip = api.tooltip, 3125 options = api.options.show.modal, 3126 effect = options.effect, 3127 type = state ? 'show': 'hide', 3128 visible = elem.is(':visible'), 3129 visibleModals = $(MODALSELECTOR).filter(':visible:not(:animated)').not(tooltip); 3130 3131 // Set active tooltip API reference 3132 self.update(api); 3133 3134 // If the modal can steal the focus... 3135 // Blur the current item and focus anything in the modal we an 3136 if(state && options.stealfocus !== FALSE) { 3137 focusInputs( $(':focus') ); 3138 } 3139 3140 // Toggle backdrop cursor style on show 3141 elem.toggleClass('blurs', options.blur); 3142 3143 // Append to body on show 3144 if(state) { 3145 elem.appendTo(document.body); 3146 } 3147 3148 // Prevent modal from conflicting with show.solo, and don't hide backdrop is other modals are visible 3149 if(elem.is(':animated') && visible === state && prevState !== FALSE || !state && visibleModals.length) { 3150 return self; 3151 } 3152 3153 // Stop all animations 3154 elem.stop(TRUE, FALSE); 3155 3156 // Use custom function if provided 3157 if($.isFunction(effect)) { 3158 effect.call(elem, state); 3159 } 3160 3161 // If no effect type is supplied, use a simple toggle 3162 else if(effect === FALSE) { 3163 elem[ type ](); 3164 } 3165 3166 // Use basic fade function 3167 else { 3168 elem.fadeTo( parseInt(duration, 10) || 90, state ? 1 : 0, function() { 3169 if(!state) { elem.hide(); } 3170 }); 3171 } 3172 3173 // Reset position and detach from body on hide 3174 if(!state) { 3175 elem.queue(function(next) { 3176 elem.css({ left: '', top: '' }); 3177 if(!$(MODALSELECTOR).length) { elem.detach(); } 3178 next(); 3179 }); 3180 } 3181 3182 // Cache the state 3183 prevState = state; 3184 3185 // If the tooltip is destroyed, set reference to null 3186 if(current.destroyed) { current = NULL; } 3187 3188 return self; 3189 } 3190 }); 3191 3192 self.init(); 3193}; 3194OVERLAY = new OVERLAY(); 3195 3196function Modal(api, options) { 3197 this.options = options; 3198 this._ns = '-modal'; 3199 3200 this.qtip = api; 3201 this.init(api); 3202} 3203 3204$.extend(Modal.prototype, { 3205 init: function(qtip) { 3206 var tooltip = qtip.tooltip; 3207 3208 // If modal is disabled... return 3209 if(!this.options.on) { return this; } 3210 3211 // Set overlay reference 3212 qtip.elements.overlay = OVERLAY.elem; 3213 3214 // Add unique attribute so we can grab modal tooltips easily via a SELECTOR, and set z-index 3215 tooltip.addClass(MODALCLASS).css('z-index', QTIP.modal_zindex + $(MODALSELECTOR).length); 3216 3217 // Apply our show/hide/focus modal events 3218 qtip._bind(tooltip, ['tooltipshow', 'tooltiphide'], function(event, api, duration) { 3219 var oEvent = event.originalEvent; 3220 3221 // Make sure mouseout doesn't trigger a hide when showing the modal and mousing onto backdrop 3222 if(event.target === tooltip[0]) { 3223 if(oEvent && event.type === 'tooltiphide' && /mouse(leave|enter)/.test(oEvent.type) && $(oEvent.relatedTarget).closest(OVERLAY.elem[0]).length) { 3224 /* eslint-disable no-empty */ 3225 try { event.preventDefault(); } 3226 catch(e) {} 3227 /* eslint-enable no-empty */ 3228 } 3229 else if(!oEvent || oEvent && oEvent.type !== 'tooltipsolo') { 3230 this.toggle(event, event.type === 'tooltipshow', duration); 3231 } 3232 } 3233 }, this._ns, this); 3234 3235 // Adjust modal z-index on tooltip focus 3236 qtip._bind(tooltip, 'tooltipfocus', function(event, api) { 3237 // If focus was cancelled before it reached us, don't do anything 3238 if(event.isDefaultPrevented() || event.target !== tooltip[0]) { return; } 3239 3240 var qtips = $(MODALSELECTOR), 3241 3242 // Keep the modal's lower than other, regular qtips 3243 newIndex = QTIP.modal_zindex + qtips.length, 3244 curIndex = parseInt(tooltip[0].style.zIndex, 10); 3245 3246 // Set overlay z-index 3247 OVERLAY.elem[0].style.zIndex = newIndex - 1; 3248 3249 // Reduce modal z-index's and keep them properly ordered 3250 qtips.each(function() { 3251 if(this.style.zIndex > curIndex) { 3252 this.style.zIndex -= 1; 3253 } 3254 }); 3255 3256 // Fire blur event for focused tooltip 3257 qtips.filter('.' + CLASS_FOCUS).qtip('blur', event.originalEvent); 3258 3259 // Set the new z-index 3260 tooltip.addClass(CLASS_FOCUS)[0].style.zIndex = newIndex; 3261 3262 // Set current 3263 OVERLAY.update(api); 3264 3265 // Prevent default handling 3266 /* eslint-disable no-empty */ 3267 try { event.preventDefault(); } 3268 catch(e) {} 3269 /* eslint-enable no-empty */ 3270 }, this._ns, this); 3271 3272 // Focus any other visible modals when this one hides 3273 qtip._bind(tooltip, 'tooltiphide', function(event) { 3274 if(event.target === tooltip[0]) { 3275 $(MODALSELECTOR).filter(':visible').not(tooltip).last().qtip('focus', event); 3276 } 3277 }, this._ns, this); 3278 }, 3279 3280 toggle: function(event, state, duration) { 3281 // Make sure default event hasn't been prevented 3282 if(event && event.isDefaultPrevented()) { return this; } 3283 3284 // Toggle it 3285 OVERLAY.toggle(this.qtip, !!state, duration); 3286 }, 3287 3288 destroy: function() { 3289 // Remove modal class 3290 this.qtip.tooltip.removeClass(MODALCLASS); 3291 3292 // Remove bound events 3293 this.qtip._unbind(this.qtip.tooltip, this._ns); 3294 3295 // Delete element reference 3296 OVERLAY.toggle(this.qtip, FALSE); 3297 delete this.qtip.elements.overlay; 3298 } 3299}); 3300 3301 3302MODAL = PLUGINS.modal = function(api) { 3303 return new Modal(api, api.options.show.modal); 3304}; 3305 3306// Setup sanitiztion rules 3307MODAL.sanitize = function(opts) { 3308 if(opts.show) { 3309 if(typeof opts.show.modal !== 'object') { opts.show.modal = { on: !!opts.show.modal }; } 3310 else if(typeof opts.show.modal.on === 'undefined') { opts.show.modal.on = TRUE; } 3311 } 3312}; 3313 3314// Base z-index for all modal tooltips (use qTip core z-index as a base) 3315/* eslint-disable camelcase */ 3316QTIP.modal_zindex = QTIP.zindex - 200; 3317/* eslint-enable camelcase */ 3318 3319// Plugin needs to be initialized on render 3320MODAL.initialize = 'render'; 3321 3322// Setup option set checks 3323CHECKS.modal = { 3324 '^show.modal.(on|blur)$': function() { 3325 // Initialise 3326 this.destroy(); 3327 this.init(); 3328 3329 // Show the modal if not visible already and tooltip is visible 3330 this.qtip.elems.overlay.toggle( 3331 this.qtip.tooltip[0].offsetWidth > 0 3332 ); 3333 } 3334}; 3335 3336// Extend original api defaults 3337$.extend(TRUE, QTIP.defaults, { 3338 show: { 3339 modal: { 3340 on: FALSE, 3341 effect: TRUE, 3342 blur: TRUE, 3343 stealfocus: TRUE, 3344 escape: TRUE 3345 } 3346 } 3347}); 3348;
3348})); 3349}( 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.