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 }, options); 68 69 70 71 72 return this.each(function(){ 73 74 var $target = $(this); 75 76 if ( settings.responsiveMode && !this.hasAttribute('data-tooltip-responsive' ) ) 77 return; 78 79 if( $target.data('tooltip.settings') && !settings.replaceExisting) 80 return; 81 82 if( settings.useClickEvent ) 83 { 84 $target.bind('click.tooltip', methods.show); 85 } 86 else 87 { 88 $target.bind('vgp_onfocus.tooltip', methods.show ); 89 $target.bind('vgp_onblur.tooltip', methods.hide ); 90 } 91 92 if( settings.useContextMenuEvent ) 93 { 94 $target.bind('contextmenu.tooltip', methods.show); 95 } 96 if( settings.useMouseEnterEvent ) 97 { 98 $target.bind('mouseenter.tooltip', methods.show); 99 } 100 if( settings.useMouseLeaveEvent ) 101 { 102 $target.bind('mouseleave.tooltip', methods.hide); 103 } 104 105 if ( settings.disableOnTouchDevice ) 106 { 107 // if we get a touch event, disable tooltip events on this object. This fires before mouse events, so we 108 // can prevent the hover from showing. 109 // (we could preventDefault(), but that will prevent the actual click event that's coming too) 110 $target.bind('touchstart.tooltip', function(e) { $target.data('inTouchEvent', true); }); 111 $target.bind('mouseup.tooltip', function(e) { $target.data('inTouchEvent', false); }); 112 } 113 114 $target.data('tooltip.settings', settings); 115 $target.data('tooltip.id', g_nextTooltipId++); 116 117 if( settings.createOnLoad ) 118 methods.gettooltip( this, settings ); 119 }); 120 121 }, 122 destroy : function() { 123 124 return this.each(function(){ 125 $(window).unbind('.tooltip'); 126 }) 127 128 }, 129 gettooltip : function( element, settings ) { 130 var $element= $(element); 131 var toolDiv = $element.data("tooltip.element"); 132 var toolId = $element.data("tooltip.id"); 133 if ( !toolDiv ) 134 {
135 toolDiv = $('<div />'); 136 137 if( settings.suppressOnClick ) 138 { 139 toolDiv.bind('click.tooltip', jQuery.proxy(methods.hide, element)); 140 } 141 toolDiv.hide(); 142 toolDiv.addClass(settings.tooltipClass); 143 if ( $element.data( 'tooltip-class' ) ) 144 { 145 toolDiv.addClass( $element.data( 'tooltip-class' ) ); 146 } 147 toolDiv.css({ 148 position: 'absolute', 149 'z-index': 1500 150 }); 151 toolDiv.attr('id', 'tooltip-' + toolId); 152 var type = $element.data('tooltip-type'); 153 if( !type ) 154 type = settings.defaultType; 155 156 var content = ''; 157 if ( settings.dataAttr ) 158 content = $element.attr( settings.dataAttr ); 159 else if ( settings.dataName ) 160 content = $element.data(settings.dataName); 161 162 if( type == 'text') 163 { 164 toolDiv.text( content ); 165 } 166 else if ( type == 'selector' ) 167 { 168 var toolElement = $( content, $element.parent() ); 169 toolDiv.data( 'originalParent', toolElement.parent() ); 170 toolDiv.append( toolElement.show() ); 171 toolDiv.data( 'preserveContent', true ); 172 } 173 else 174 { 175 toolDiv.html( content ); 176 } 177 178 if ( typeof( g_TextFilter ) !== 'undefined' && g_TextFilter != null ) 179 { 180 g_TextFilter.FilterElement( toolDiv ); 181 } 182 183 $( settings.tooltipParent || $element.parent() ).append(toolDiv); 184 $element.data("tooltip.element", toolDiv); 185 186 } 187 188 var funcName = settings.funcName && $(element).data( settings.funcName ); 189 if( funcName ) 190 { 191 if( type == 'text') 192 toolDiv.text( window[funcName](element) ); 193 else 194 toolDiv.html( window[funcName](element) ); 195 } 196 197 if( settings.func ) 198 settings.func.bind(toolDiv)(element); 199 200 return toolDiv; 201 }, 202 updateposition : function() { 203 var newPosition = {}; 204 var settings = $(this).data('tooltip.settings'); 205 var toolDiv = methods.gettooltip( this, settings ); 206 207 var parentPosition = $(this).offset(); 208 if( settings.tooltipParent != 'body' ) 209 parentPosition = $(this).position(); 210 211 const bRTL = toolDiv[0].matches( ':dir(rtl)' ); 212 switch( settings.location ) 213 { 214 case 'top': 215 if ( bRTL ) 216 { 217 newPosition = { 218 left: parentPosition.left + $(this).outerWidth() - toolDiv.outerWidth() + settings.offsetX, 219 top: parentPosition.top - toolDiv.outerHeight() + settings.offsetY 220 }; 221 } 222 else 223 { 224 newPosition = { 225 left: parentPosition.left + settings.offsetX, 226 top: parentPosition.top - toolDiv.outerHeight() + settings.offsetY 227 }; 228 } 229 break; 230 231 case 'bottom': 232 if ( bRTL ) 233 { 234 newPosition = { 235 left: parentPosition.left + $(this).outerWidth() - toolDiv.outerWidth() + settings.offsetX, 236 top: parentPosition.top + $(this).outerHeight() + settings.offsetY 237 }; 238 } 239 else 240 { 241 newPosition = { 242 left: parentPosition.left + settings.offsetX, 243 top: parentPosition.top + $(this).outerHeight() + settings.offsetY 244 }; 245 } 246 break; 247 248 case 'bottom left': 249 if ( bRTL ) 250 { 251 newPosition = { 252 left: parentPosition.left + settings.offsetX, 253 top: parentPosition.top + $(this).outerHeight() + settings.offsetY 254 }; 255 } 256 else 257 { 258 newPosition = { 259 left: parentPosition.left + settings.offsetX - toolDiv.outerWidth() + $J(this).outerWidth(), 260 top: parentPosition.top + $(this).outerHeight() + settings.offsetY 261 }; 262 } 263 break; 264 265 default: 266 console.log("Invalid location passed to v_tooltip: %s", settings.location); 267 } 268 // Correct for window size 269 if( settings.correctForScreenSize ) 270 { 271 var rightEdge = newPosition.left + toolDiv.width(); 272 var windowRightEdge = $(window).width() - settings.sizeCorrectionXPadding + $(window).scrollLeft(); 273 var windowLeftEdge = $(window).scrollLeft() + settings.sizeCorrectionXPadding; 274 275 if( rightEdge > windowRightEdge ) 276 newPosition.left = windowRightEdge - toolDiv.width() - settings.sizeCorrectionXPadding; 277 278 if( newPosition.left < windowLeftEdge )
279 newPosition.left = windowLeftEdge; 280 281 if ( newPosition.top < 0 ) 282 newPosition.top = parentPosition.top + $(this).height() + settings.sizeCorrectionYPadding - settings.offsetY; 283 } 284 285 toolDiv.css(newPosition); 286 }, 287 reposition : function(event) { 288 var newPosition = {}; 289 var settings = $(this).data('tooltip.settings'); 290 var toolDiv = methods.gettooltip( this, settings ); 291 292 var parentPosition = $(this).offset(); 293 if( settings.tooltipParent != 'body' ) 294 parentPosition = $(this).position(); 295 296 if( settings.trackMouse ) 297 { 298 if ( settings.trackMouseCentered ) 299 newPosition.left = event.pageX - toolDiv.outerWidth() / 2; 300 else 301 newPosition.left = event.pageX + settings.offsetY; 302 303 if ( settings.location == 'top' ) 304 newPosition.top = event.pageY - toolDiv.outerHeight() + settings.offsetY; 305 else 306 newPosition.top = event.pageY + settings.offsetY; 307 308 } else { 309 const bRTL = toolDiv[0].matches( ':dir(rtl)' ); 310 switch( settings.location ) 311 { 312 case 'top': 313 if ( bRTL ) 314 { 315 newPosition = { 316 left: parentPosition.left + $(this).outerWidth() - toolDiv.outerWidth() + settings.offsetX, 317 top: parentPosition.top - toolDiv.outerHeight() + settings.offsetY 318 }; 319 } 320 else 321 { 322 newPosition = { 323 left: parentPosition.left + settings.offsetX, 324 top: parentPosition.top - toolDiv.outerHeight() + settings.offsetY 325 }; 326 } 327 break; 328 329 case 'bottom': 330 if ( bRTL ) 331 { 332 newPosition = { 333 left: parentPosition.left + $(this).outerWidth() - toolDiv.outerWidth() + settings.offsetX, 334 top: parentPosition.top + $(this).outerHeight() + settings.offsetY 335 }; 336 } 337 else 338 { 339 newPosition = { 340 left: parentPosition.left + settings.offsetX, 341 top: parentPosition.top + $(this).outerHeight() + settings.offsetY 342 }; 343 } 344 break; 345 346 case 'bottom left': 347 if ( bRTL ) 348 { 349 newPosition = { 350 left: parentPosition.left + settings.offsetX, 351 top: parentPosition.top + $(this).outerHeight() + settings.offsetY 352 }; 353 } 354 else 355 { 356 newPosition = { 357 left: parentPosition.left + settings.offsetX - toolDiv.outerWidth() + $J(this).outerWidth(), 358 top: parentPosition.top + $(this).outerHeight() + settings.offsetY 359 }; 360 } 361 break; 362 363 case 'mouse': 364 newPosition = { 365 left: event.pageX + settings.offsetX, 366 top: event.pageY + settings.offsetY 367 } 368 369 default: 370 console.log("Invalid location passed to v_tooltip: %s", settings.location); 371 } 372 } 373 // Correct for window size 374 if( settings.correctForScreenSize ) 375 { 376 var rightEdge = newPosition.left + toolDiv.width(); 377 var windowRightEdge = $(window).width() - settings.sizeCorrectionXPadding + $(window).scrollLeft(); 378 var windowLeftEdge = $(window).scrollLeft() + settings.sizeCorrectionXPadding; 379 380 if( rightEdge > windowRightEdge ) 381 newPosition.left = windowRightEdge - toolDiv.width() - settings.sizeCorrectionXPadding; 382 383 if( newPosition.left < windowLeftEdge ) 384 newPosition.left = windowLeftEdge; 385 386 if ( newPosition.top < 0 ) 387 newPosition.top = parentPosition.top + $(this).height() + settings.sizeCorrectionYPadding - settings.offsetY; 388 } 389 390 toolDiv.css(newPosition); 391 }, 392 show : function(event) { 393 var settings = $(this).data('tooltip.settings') || {}; 394 395 if ( settings.disableOnTouchDevice && $(this ).data('inTouchEvent') ) 396 { 397 return; 398 } 399 400 var toolDiv = methods.gettooltip( this, settings ); 401 402 if( toolDiv.is(':empty') ) 403 return; 404 405 if( event.type == "click" && event.currentTarget != this ) 406 return; 407 408 if( settings.suppressWhileToggled && $(this).hasClass('toggled') ) 409 return false; 410 411 var tipElem = this; 412 toolDiv.find( 'img' ).on( 'load', function() { jQuery.proxy(methods.updateposition, tipElem)() } ); 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 if( settings.fadeSpeed > 0 ) 431 { 432 toolDiv.stop(true, true); 433 toolDiv.fadeTo( settings.fadeSpeed, 1 ); 434 } 435 else 436 toolDiv.show(); 437 438 if( settings.allowHover ) 439 { 440 if( settings.useClickEvent ) 441 toolDiv.bind('click.tooltip', jQuery.proxy(methods.show, this)); 442 else if( settings.useContextMenuEvent ) 443 toolDiv.bind('contextmenu.tooltip', jQuery.proxy(methods.show, this)); 444 else 445 toolDiv.bind('mouseenter.tooltip', jQuery.proxy(methods.show, this)); 446 toolDiv.bind('mouseleave.tooltip', jQuery.proxy(methods.hide, this)); 447 } 448 449 if( settings.trackMouse ) 450 $(this).bind('mousemove.tooltip', methods.reposition); 451 else 452 jQuery.proxy(methods.reposition, this)(event); 453 454 if( settings.childActiveCSSClass ) 455 { 456 toolDiv.css('opacity'); 457 toolDiv.addClass(settings.childActiveCSSClass); 458 459 } 460 461 toolDiv.css('pointer-events','auto'); 462 463 $(this).trigger('v_tooltip_shown', [ toolDiv ] ); 464
465 $(this).attr('aria-describedby', 'tooltip-' + $(this).data("tooltip.id")); 466 467 }, 468 hide : function(event) { 469 var toolDiv = $(this).data('tooltip.element'); 470 var settings = $(this).data('tooltip.settings') || {}; 471 472 // the element may not have been created yet - in which case there is nothing to hide 473 if ( !toolDiv || !toolDiv.length ) 474 return; 475 476 477 if( event && event.type != 'click' ) 478 { 479 // Moving between the click target and it's children 480 if( event.relatedTarget && ( this == event.relatedTarget || this.contains(event.relatedTarget) ) ) 481 return; 482 // Moving to the tooltip or one of it's children 483 else if( event.relatedTarget && ( event.relatedTarget == toolDiv[0] || toolDiv[0].contains(event.relatedTarget ) ) ) 484 return; 485 486 487 488 } 489 490 if( settings.trackMouse ) 491 $(this).unbind('mousemove.tooltip'); 492 493 toolDiv.unbind('mouseenter.tooltip'); 494 toolDiv.unbind('mouseleave.tooltip'); 495 496 if( settings.parentActiveCSSClass ) 497 $(this).removeClass(settings.parentActiveCSSClass); 498 499 500 if( settings.fadeSpeed > 0 && toolDiv.is(':visible') ) 501 { 502 toolDiv.stop(); 503 toolDiv.fadeTo( settings.fadeSpeed, 0, function() { if( settings.destroyWhenDone ) methods.destroytooltip( toolDiv ) } ); 504 } else if( settings.childActiveCSSClass ) 505 { 506 toolDiv.css('opacity'); 507 toolDiv.removeClass(settings.childActiveCSSClass); 508 // Note: This callback isn't reliable, so we may end up junking up the dom with a few spare copies 509 // Not a huge deal in most cases, but keep in mind if you're using this on pages which may exist for 510 // long periods of time without reloading. 511 if( settings.destroyWhenDone ) 512 { 513 toolDiv.on ( "transitionend", function () { 514 methods.destroytooltip ( toolDiv ) 515 } ); 516 517 // Transitionend may not fire under various conditions, so JUST IN CASE.... 518 setTimeout( function(){ methods.destroytooltip ( toolDiv ); }, 1000); 519 } 520 } 521 else 522 { 523 if( settings.destroyWhenDone ) 524 methods.destroytooltip( toolDiv ); 525 } 526 if( settings.destroyWhenDone ) 527 $(this).removeData('tooltip.element'); 528 else 529 toolDiv.css('pointer-events','none'); 530 531 $(this).trigger( 'v_tooltip_hidden' ); 532 }, 533 destroytooltip: function( toolDiv ) 534 { 535 if ( toolDiv ) 536 { 537 if ( $(toolDiv ).data('preserveContent') ) 538 { 539 $( toolDiv.data( 'originalParent') ).append( $(toolDiv ).children().hide()); 540 } 541 542 $(toolDiv).remove(); 543 } 544 } 545 }; 546 547 $.fn.v_tooltip = function( method ) { 548 549 if ( methods[method] ) { 550 return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 )); 551 } else if ( typeof method === 'object' || ! method ) { 552 return methods.init.apply( this, arguments ); 553 } else { 554 $.error( 'Method ' + method + ' does not exist on jQuery.tooltip' ); 555 } 556 557 }; 558 559 560})( jQuery ); 561 562
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.