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