1/* Requires jQuery 2 * 3 * This plugin will create div.jsTooltip elements (or configure your own!) in body for every tooltip on the page. Some 4 * basic CSS is applied automagically, but you'll want to style it on your own from there. This code will be applied to 5 * every element in your .v_tooltip() selector, so giving it a common selector like '.tooltip' is ideal. 6 * 7 * Options: 8 * - location: Where the tooltip should spawn in relation to it's parent 9 * - offsetN: How many pixels to add 10 * - trackMouse: Should we track the mouse cursor instead of the parent? 11 * - suppressOnClick: Should we hide if a user clicks the target? 12 * - suppressWhileToggled: Should we ignore events if the target has the 'toggled' class? 13 * - tooltipClass: css class to apply to tooltip elements 14 * - fadeSpeed: Time (in milliseconds) to spend fading in/out. Set to 0 to disable. 15 * - allowHover: Should we keep the tooltip open if we mouse directly on to the tooltip? (Your tooltip will need to spawn inside it's owner's box for this to work) 16 * - tooltipParent: More generally useless properties for supernav: Lets us specify which element to parent the tooltips to. YOU PROBABLY DON'T NEED THIS. 17 * - correctForScreenSize: Adjust tooltip position to ensure it doesn't render outside of the viewport 18 * - sizeCorrectionXPadding: How far we should keep the tooltip from the window edge 19 * - useClickEvent: Should we use the mouse click event instead of hover? 20 * - inheritParentMinWidth: Should we set min-width based on our parent's width? 21 * - parentActiveCSSClass: What CSS class should we add to our parent while we're visible? 22 * - childActiveCSSClass: What CSS class should we add to the tooltip when active (Mostly useful for triggering CSS transitions 23 * - funcName: Global name of a function to call on hover (It searches the global window object; not safe inside "use strict" 24 * - func: Actual JS code to run. 25 * - responsiveMode: enable when in responsive mode/mobile/tablet, requires tooltip elements to be decorated with data-tooltip-responsive to enable in responsive mode 26 */ 27/* <script> */ 28(function( $ ){ 29 let g_nextTooltipId = 0; 30 var methods = { 31 32 init : function( options ) { 33 34 var settings = $.extend( { 35 'location' : 'top', 36 'offsetX' : 0, 37 'offsetY' : -10, 38 'trackMouse' : false, 39 'trackMouseCentered': true, 40 'suppressOnClick' : true, 41 'suppressWhileToggled': true, 42 'tooltipClass' : 'jsTooltip', 43 'fadeSpeed' : 150, 44 'allowHover' : true, 45 'tooltipParent' : 'body', 46 'correctForScreenSize': true, 47 'sizeCorrectionXPadding': 15, 48 'sizeCorrectionYPadding': 10, 49 'useClickEvent' : false, 50 'useContextMenuEvent' : false, 51 'useMouseEnterEvent' : true, 52 'useMouseLeaveEvent' : true, 53 'preventDefault' : true, 54 'stopPropagation' : false, 55 'inheritParentMinWidth' : false, 56 'parentActiveCSSClass' : false, 57 'dataName' : 'tooltipContent', 58 'funcName' : 'tooltipFunc', 59 'func' : false, 60 'disableOnTouchDevice' : false, 61 'childActiveCSSClass' : false, 62 'destroyWhenDone': true, 63 'createOnLoad': false, 64 'replaceExisting': true, // should we stomp on existing tooltips? 65 'defaultType': 'html', // default tooltip type if not specified. 66 'responsiveMode': false, 67 'appearanceDelayMs': 0, // How long do we wait after the trigger to show the tooltip (or to start fade-in) 68 }, options); 69 70 71 72 73 return this.each(function(){ 74 75 var $target = $(this); 76 77 if ( settings.responsiveMode && !this.hasAttribute('data-tooltip-responsive' ) ) 78 return; 79 80 if( $target.data('tooltip.settings') && !settings.replaceExisting) 81 return; 82 83 if( settings.useClickEvent ) 84 { 85 $target.bind('click.tooltip', methods.show); 86 } 87 else 88 { 89 $target.bind('vgp_onfocus.tooltip', methods.show ); 90 $target.bind('vgp_onblur.tooltip', methods.hide ); 91 } 92 93 if( settings.useContextMenuEvent ) 94 { 95 $target.bind('contextmenu.tooltip', methods.show); 96 } 97 if( settings.useMouseEnterEvent ) 98 { 99 $target.bind('mouseenter.tooltip', methods.show); 100 } 101 if( settings.useMouseLeaveEvent ) 102 { 103 $target.bind('mouseleave.tooltip', methods.hide); 104 } 105 106 if ( settings.disableOnTouchDevice ) 107 { 108 // if we get a touch event, disable tooltip events on this object. This fires before mouse events, so we 109 // can prevent the hover from showing. 110 // (we could preventDefault(), but that will prevent the actual click event that's coming too) 111 $target.bind('touchstart.tooltip', function(e) { $target.data('inTouchEvent', true); }); 112 $target.bind('mouseup.tooltip', function(e) { $target.data('inTouchEvent', false); }); 113 } 114 115 $target.data('tooltip.settings', settings); 116 $target.data('tooltip.id', g_nextTooltipId++); 117 118 if( settings.createOnLoad ) 119 methods.gettooltip( this, settings ); 120 }); 121 122 }, 123 destroy : function() { 124 125 return this.each(function(){ 126 $(window).unbind('.tooltip'); 127 }) 128 129 }, 130 gettooltip : function( element, settings ) { 131 var $element= $(element); 132 var toolDiv = $element.data("tooltip.element"); 133 var toolId = $element.data("tooltip.id"); 134 if ( !toolDiv ) 135 {
136 toolDiv = $('<div />'); 137 138 if( settings.suppressOnClick ) 139 { 140 toolDiv.bind('click.tooltip', jQuery.proxy(methods.hide, element)); 141 } 142 toolDiv.hide(); 143 toolDiv.addClass(settings.tooltipClass); 144 if ( $element.data( 'tooltip-class' ) ) 145 { 146 toolDiv.addClass( $element.data( 'tooltip-class' ) ); 147 } 148 toolDiv.css({ 149 position: 'absolute', 150 'z-index': 1500 151 }); 152 toolDiv.attr('id', 'tooltip-' + toolId); 153 var type = $element.data('tooltip-type'); 154 if( !type ) 155 type = settings.defaultType; 156 157 var content = ''; 158 if ( settings.dataAttr ) 159 content = $element.attr( settings.dataAttr ); 160 else if ( settings.dataName ) 161 content = $element.data(settings.dataName); 162 163 if( type == 'text') 164 { 165 toolDiv.text( content ); 166 } 167 else if ( type == 'selector' ) 168 { 169 var toolElement = $( content, $element.parent() ); 170 toolDiv.data( 'originalParent', toolElement.parent() ); 171 toolDiv.append( toolElement.show() ); 172 toolDiv.data( 'preserveContent', true ); 173 } 174 else 175 { 176 toolDiv.html( content ); 177 } 178 179 if ( typeof( g_TextFilter ) !== 'undefined' && g_TextFilter != null ) 180 { 181 g_TextFilter.FilterElement( toolDiv ); 182 } 183 184 $( settings.tooltipParent || $element.parent() ).append(toolDiv); 185 $element.data("tooltip.element", toolDiv); 186 187 } 188 189 var funcName = settings.funcName && $(element).data( settings.funcName ); 190 if( funcName ) 191 { 192 if( type == 'text') 193 toolDiv.text( window[funcName](element) ); 194 else 195 toolDiv.html( window[funcName](element) ); 196 } 197 198 if( settings.func ) 199 settings.func.bind(toolDiv)(element); 200 201 return toolDiv; 202 }, 203 updateposition : function() { 204 var newPosition = {}; 205 var settings = $(this).data('tooltip.settings'); 206 var toolDiv = methods.gettooltip( this, settings ); 207 208 var parentPosition = $(this).offset(); 209 210 const bRTL = toolDiv[0].matches( ':dir(rtl)' ); 211 switch( settings.location ) 212 { 213 case 'top': 214 if ( bRTL ) 215 { 216 newPosition = { 217 left: parentPosition.left + $(this).outerWidth() - toolDiv.outerWidth() + settings.offsetX, 218 top: parentPosition.top - toolDiv.outerHeight() + settings.offsetY 219 }; 220 } 221 else 222 { 223 newPosition = { 224 left: parentPosition.left + settings.offsetX, 225 top: parentPosition.top - toolDiv.outerHeight() + settings.offsetY 226 }; 227 } 228 break; 229 230 case 'bottom': 231 if ( bRTL ) 232 { 233 newPosition = { 234 left: parentPosition.left + $(this).outerWidth() - toolDiv.outerWidth() + settings.offsetX, 235 top: parentPosition.top + $(this).outerHeight() + settings.offsetY 236 }; 237 } 238 else 239 { 240 newPosition = { 241 left: parentPosition.left + settings.offsetX, 242 top: parentPosition.top + $(this).outerHeight() + settings.offsetY 243 }; 244 } 245 break; 246 247 case 'bottom left': 248 if ( bRTL ) 249 { 250 newPosition = { 251 left: parentPosition.left + settings.offsetX, 252 top: parentPosition.top + $(this).outerHeight() + settings.offsetY 253 }; 254 } 255 else 256 { 257 newPosition = { 258 left: parentPosition.left + settings.offsetX - toolDiv.outerWidth() + $J(this).outerWidth(), 259 top: parentPosition.top + $(this).outerHeight() + settings.offsetY 260 }; 261 } 262 break; 263 264 default: 265 console.log("Invalid location passed to v_tooltip: %s", settings.location); 266 } 267 // Correct for window size 268 if( settings.correctForScreenSize ) 269 { 270 var rightEdge = newPosition.left + toolDiv.width(); 271 var windowRightEdge = $(window).width() - settings.sizeCorrectionXPadding + $(window).scrollLeft(); 272 var windowLeftEdge = $(window).scrollLeft() + settings.sizeCorrectionXPadding; 273 274 if( rightEdge > windowRightEdge ) 275 newPosition.left = windowRightEdge - toolDiv.width() - settings.sizeCorrectionXPadding; 276 277 if( newPosition.left < windowLeftEdge ) 278 newPosition.left = windowLeftEdge; 279 280 if ( newPosition.top < 0 ) 281 newPosition.top = parentPosition.top + $(this).height() + settings.sizeCorrectionYPadding - settings.offsetY; 282 } 283 284 toolDiv.css(newPosition); 285 }, 286 reposition : function(event) { 287 var newPosition = {}; 288 var settings = $(this).data('tooltip.settings'); 289 var toolDiv = methods.gettooltip( this, settings ); 290 291 var parentPosition = $(this).offset(); 292 293 if( settings.trackMouse ) 294 { 295 if ( settings.trackMouseCentered ) 296 newPosition.left = event.pageX - toolDiv.outerWidth() / 2; 297 else 298 newPosition.left = event.pageX + settings.offsetY; 299 300 if ( settings.location == 'top' ) 301 newPosition.top = event.pageY - toolDiv.outerHeight() + settings.offsetY; 302 else 303 newPosition.top = event.pageY + settings.offsetY; 304 305 } else { 306 const bRTL = toolDiv[0].matches( ':dir(rtl)' ); 307 switch( settings.location ) 308 { 309 case 'top': 310 if ( bRTL ) 311 { 312 newPosition = { 313 left: parentPosition.left + $(this).outerWidth() - toolDiv.outerWidth() + settings.offsetX, 314 top: parentPosition.top - toolDiv.outerHeight() + settings.offsetY 315 }; 316 } 317 else 318 { 319 newPosition = { 320 left: parentPosition.left + settings.offsetX, 321 top: parentPosition.top - toolDiv.outerHeight() + settings.offsetY 322 }; 323 } 324 break; 325 326 case 'bottom': 327 if ( bRTL ) 328 { 329 newPosition = { 330 left: parentPosition.left + $(this).outerWidth() - toolDiv.outerWidth() + settings.offsetX, 331 top: parentPosition.top + $(this).outerHeight() + settings.offsetY 332 }; 333 } 334 else 335 { 336 newPosition = { 337 left: parentPosition.left + settings.offsetX, 338 top: parentPosition.top + $(this).outerHeight() + settings.offsetY 339 }; 340 } 341 break; 342 343 case 'bottom left': 344 if ( bRTL ) 345 { 346 newPosition = { 347 left: parentPosition.left + settings.offsetX, 348 top: parentPosition.top + $(this).outerHeight() + settings.offsetY 349 }; 350 } 351 else 352 { 353 newPosition = { 354 left: parentPosition.left + settings.offsetX - toolDiv.outerWidth() + $J(this).outerWidth(), 355 top: parentPosition.top + $(this).outerHeight() + settings.offsetY 356 }; 357 } 358 break; 359 360 case 'mouse': 361 newPosition = { 362 left: event.pageX + settings.offsetX, 363 top: event.pageY + settings.offsetY 364 } 365 366 default: 367 console.log("Invalid location passed to v_tooltip: %s", settings.location); 368 } 369 } 370 // Correct for window size 371 if( settings.correctForScreenSize ) 372 { 373 var rightEdge = newPosition.left + toolDiv.width(); 374 var windowRightEdge = $(window).width() - settings.sizeCorrectionXPadding + $(window).scrollLeft();
375 var windowLeftEdge = $(window).scrollLeft() + settings.sizeCorrectionXPadding; 376 377 if( rightEdge > windowRightEdge ) 378 newPosition.left = windowRightEdge - toolDiv.width() - settings.sizeCorrectionXPadding; 379 380 if( newPosition.left < windowLeftEdge ) 381 newPosition.left = windowLeftEdge; 382 383 if ( newPosition.top < 0 ) 384 newPosition.top = parentPosition.top + $(this).height() + settings.sizeCorrectionYPadding - settings.offsetY; 385 } 386 387 toolDiv.css(newPosition); 388 }, 389 show : function(event) { 390 var settings = $(this).data('tooltip.settings') || {}; 391 392 if ( settings.disableOnTouchDevice && $(this ).data('inTouchEvent') ) 393 { 394 return; 395 } 396 397 var toolDiv = methods.gettooltip( this, settings ); 398 399 if( toolDiv.is(':empty') ) 400 return; 401 402 if( event.type == "click" && event.currentTarget != this ) 403 return; 404 405 if( settings.suppressWhileToggled && $(this).hasClass('toggled') ) 406 return false; 407 408 var tipElem = this; 409 if ( !settings.trackMouse ) 410 { 411 toolDiv.find( 'img' ).on( 'load', function() { jQuery.proxy(methods.updateposition, tipElem)() } ); 412 } 413 414 if( settings.preventDefault ) 415 event.preventDefault(); 416 417 if( settings.stopPropagation ) 418 event.stopPropagation(); 419 420 if( settings.parentActiveCSSClass ) 421 $(this).addClass(settings.parentActiveCSSClass); 422 423 if( settings.inheritParentMinWidth ) 424 { 425 var parentWidth = $(this).outerWidth(); 426 var localPadding = toolDiv.outerWidth() - toolDiv.width(); 427 toolDiv.css({'min-width': + (parentWidth - localPadding) + "px"}); 428 } 429 430 toolDiv[0].popover = 'manual'; 431 toolDiv.css('inset', 'unset'); 432 toolDiv.css('border', 'none'); 433 toolDiv.css('overflow', 'initial'); 434 435 toolDiv.stop(true, true); 436 if( settings.fadeSpeed > 0 ) 437 toolDiv.delay( settings.appearanceDelayMs ).fadeTo( settings.fadeSpeed, 1 ); 438 else 439 toolDiv.delay( settings.appearanceDelayMs ).show(); 440 441 if( settings.allowHover ) 442 { 443 if( settings.useClickEvent ) 444 toolDiv.bind('click.tooltip', jQuery.proxy(methods.show, this)); 445 else if( settings.useContextMenuEvent ) 446 toolDiv.bind('contextmenu.tooltip', jQuery.proxy(methods.show, this)); 447 else 448 toolDiv.bind('mouseenter.tooltip', jQuery.proxy(methods.show, this)); 449 toolDiv.bind('mouseleave.tooltip', jQuery.proxy(methods.hide, this)); 450 } 451 452 if( settings.trackMouse ) 453 $(this).bind('mousemove.tooltip', methods.reposition); 454 jQuery.proxy(methods.reposition, this)(event); 455 456 if( settings.childActiveCSSClass ) 457 { 458 toolDiv.css('opacity'); 459 toolDiv.addClass(settings.childActiveCSSClass); 460 461 } 462 463 toolDiv[0].showPopover(); 464 465 toolDiv.css('pointer-events','auto'); 466 467 $(this).trigger('v_tooltip_shown', [ toolDiv ] ); 468 469 $(this).attr('aria-describedby', 'tooltip-' + $(this).data("tooltip.id")); 470 471 }, 472 hide : function(event) { 473 var toolDiv = $(this).data('tooltip.element'); 474 var settings = $(this).data('tooltip.settings') || {}; 475 476 // the element may not have been created yet - in which case there is nothing to hide 477 if ( !toolDiv || !toolDiv.length ) 478 return; 479 480 481 if( event && event.type != 'click' ) 482 { 483 // Moving between the click target and it's children 484 if( event.relatedTarget && ( this == event.relatedTarget || this.contains(event.relatedTarget) ) ) 485 return; 486 // Moving to the tooltip or one of it's children 487 else if( event.relatedTarget && ( event.relatedTarget == toolDiv[0] || toolDiv[0].contains(event.relatedTarget ) ) ) 488 return; 489 } 490 491 if( settings.trackMouse ) 492 $(this).unbind('mousemove.tooltip'); 493 494 toolDiv.unbind('mouseenter.tooltip'); 495 toolDiv.unbind('mouseleave.tooltip'); 496 497 if( settings.parentActiveCSSClass ) 498 $(this).removeClass(settings.parentActiveCSSClass); 499 500 501 // Clear the animation queue so appearance delays and fading are aborted 502 toolDiv.stop( true ); 503 if( settings.fadeSpeed > 0 && toolDiv.is(':visible') ) 504 { 505 toolDiv.fadeTo( settings.fadeSpeed, 0, function() { 506 if ( settings.destroyWhenDone ) 507 { 508 methods.destroytooltip( toolDiv ); 509 } 510 if ( toolDiv[0].popover ) 511 { 512 toolDiv[0].hidePopover(); 513 } 514 } ); 515 } else if( settings.childActiveCSSClass ) 516 { 517 toolDiv.css('opacity'); 518 toolDiv.removeClass(settings.childActiveCSSClass); 519 // Note: This callback isn't reliable, so we may end up junking up the dom with a few spare copies 520 // Not a huge deal in most cases, but keep in mind if you're using this on pages which may exist for 521 // long periods of time without reloading. 522 if( settings.destroyWhenDone ) 523 { 524 toolDiv.on ( "transitionend", function () { 525 methods.destroytooltip ( toolDiv ); 526 if ( toolDiv[0].popover ) 527 { 528 toolDiv[0].hidePopover(); 529 } 530 } ); 531 532 // Transitionend may not fire under various conditions, so JUST IN CASE.... 533 setTimeout( function(){ 534 methods.destroytooltip ( toolDiv ); 535 if ( toolDiv[0].popover ) 536 { 537 toolDiv[0].hidePopover(); 538 } 539 }, 1000); 540 } 541 else 542 { 543 if ( toolDiv[0].popover ) 544 { 545 toolDiv[0].hidePopover(); 546 } 547 } 548 } 549 else 550 {
551 if( settings.destroyWhenDone ) 552 methods.destroytooltip( toolDiv ); 553 554 if ( toolDiv[0].popover ) 555 { 556 toolDiv[0].hidePopover(); 557 } 558 } 559 if( settings.destroyWhenDone ) 560 $(this).removeData('tooltip.element'); 561 else 562 toolDiv.css('pointer-events','none'); 563 564 $(this).trigger( 'v_tooltip_hidden' ); 565 }, 566 destroytooltip: function( toolDiv ) 567 { 568 if ( toolDiv ) 569 { 570 if ( $(toolDiv ).data('preserveContent') ) 571 { 572 $( toolDiv.data( 'originalParent') ).append( $(toolDiv ).children().hide()); 573 } 574 575 $(toolDiv).remove(); 576 } 577 } 578 }; 579 580 $.fn.v_tooltip = function( method ) { 581 582 if ( methods[method] ) { 583 return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 )); 584 } else if ( typeof method === 'object' || ! method ) { 585 return methods.init.apply( this, arguments ); 586 } else { 587 $.error( 'Method ' + method + ' does not exist on jQuery.tooltip' ); 588 } 589 590 }; 591 592 593})( jQuery ); 594 595
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.