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