elements.' );
return;
}
// Wrap the tooltipButton with a generated , to allow for
// easier isolation of the hover events.
tooltipButton.wrap( COC.Tooltip.html.wrapper );
// Give the wrapper a unique ID, to allow for easier isolation.
// of the click events.
tooltipButton.parents( COC.Tooltip.selector.wrapper ).attr( 'id', COC.Tooltip.generateUID( 'tooltip' ) );
// Generate the tooltip HTML and place it after the tooltipButton.
tooltipButton.after( COC.Tooltip.html.tooltip );
// Initialize variables.
var tooltip = tooltipButton.next( COC.Tooltip.selector.tooltip );
// Create hover listener.
tooltipButton.parent( COC.Tooltip.selector.wrapper ).hover(
// mouseenter
function() {
COC.Tooltip.mouseEnter( tooltipButton, tooltip );
},
// mouseleave
function() {
COC.Tooltip.mouseLeave( tooltip );
}
);
// Create click listener.
tooltipButton.click( function() {
COC.Tooltip.click( tooltipButton, tooltip );
});
},
/**
* mouseEnter()
* Occurs when mouse enters the element
*/
mouseEnter: function( tooltipButton, tooltip ) {
if( tooltip.attr( COC.Tooltip.data.trigger.attr ) === COC.Tooltip.data.trigger.click ) {
// Do nothing if the tooltip was triggered by click.
// If the data-trigger is populated, it means it is already opened.
return;
}
tooltip.attr( COC.Tooltip.data.trigger.attr, COC.Tooltip.data.trigger.hover );
COC.Tooltip.showTooltip( tooltipButton, tooltip );
},
/**
* mouseLeave()
* Occurs when mouse leaves the element.
*/
mouseLeave: function( tooltip ) {
if( tooltip.attr( COC.Tooltip.data.trigger.attr ) === COC.Tooltip.data.trigger.hover ) {
COC.Tooltip.hideTooltip( tooltip );
}
},
/**
* click()
* Occurs when element is clicked.
*/
click: function( tooltipButton, tooltip ) {
// Save the original trigger.
var originalTrigger = tooltip.attr( COC.Tooltip.data.trigger.attr );
// Update the trigger.
tooltip.attr( COC.Tooltip.data.trigger.attr, COC.Tooltip.data.trigger.click );
// Check if the original trigger is hover.
if( originalTrigger === COC.Tooltip.data.trigger.hover ) {
// Tooltip is already open. Exit.
return;
}
// MC.2018.11.05: For Assistive Technology users, 2 things are needed:
// 1. Clear the tooltip text
// 2. Set a timeout
// Every time the button is clicked, the text should be read aloud.
// The tooltip text is placed in a live-region and the 2 items above are
// required for screen readers to read the text (mimicking a faux update).
// This is particularly necessary when using VoiceOver on Safari.
tooltip.find( COC.Tooltip.selector.inner ).text( '' );
if( COC.Tooltip.event.clickTimeout ) {
window.clearTimeout( COC.Tooltip.event.clickTimeout );
}
// Set timeout.
COC.Tooltip.event.clickTimeout = window.setTimeout( COC.Tooltip.showTooltip, 100, tooltipButton, tooltip );
},// End of click()
/**
* documentKeyDown()
* Sets up the document keydown listener for a specific tooltip.
* If the [Esc] or [Tab] key is pressed, the tooltip is hidden.
*/
documentKeyDown: function( tooltip ) {
var keydownNamespace = 'keydown' + COC.Tooltip.event.namespace;
$( document ).on( keydownNamespace, function(e) {
if( ((e.keyCode || e.which) === 27) || ((e.keyCode || e.which) === 9) ) {
// Hide modal when [Esc] key or [Tab] key is pressed.
COC.Tooltip.hideTooltip( tooltip );
$( document ).off( keydownNamespace );
}
});
},
/**
* documentClick()
* Sets up the document click listener for a specific tooltip
* If an element other than the tooltip's wrapper is clicked,
* the tooltip is hidden.
*/
documentClick: function( tooltip ) {
// Keep track of the tooltip wrapper UID.
var wrapperUID = tooltip.closest( COC.Tooltip.selector.wrapper ).attr( 'id' );
// Create a unique namespace.
var clickNamespace = 'click' + COC.Tooltip.event.namespace + '.' + wrapperUID;
// Remove any existing document.click listeners and create a
// new document.click listener.
$( document ).off( clickNamespace ).on( clickNamespace, function(e) {
var clickedWrapper = $(e.target).closest( COC.Tooltip.selector.wrapper );
var fc;// flow control
// Detect if a wrapper was clicked.
if( clickedWrapper.length ) {
// A wrapper was clicked
if( clickedWrapper.is( '#' + wrapperUID ) ) {
// The original wrapper was clicked; do nothing.
fc = 0;
}
else {
// A different wrapper was clicked; hide the original
// tooltip.
fc = 1;
}
}
else {
// No wrapper clicked; hide the tooltip.
fc = 1;
}
if( fc === 1 ) {
COC.Tooltip.hideTooltip( tooltip );
$( document ).off( clickNamespace );
}
});
},
/**
* showTooltip()
* Show the specified tooltip.
*/
showTooltip: function( tooltipButton, tooltip ) {
// Update the tooltip text.
tooltip.find( COC.Tooltip.selector.inner ).text( tooltipButton.attr( COC.Tooltip.data.title.attr ) );
// Determine the tooltip placement.
// If no placement attribute specified, the default will be used.
// var validPlacementValues = [ 'top', 'right', 'bottom', 'left' ];
// MC.2018.11.13: The left placement is currently disabled,
// as it is not a preferred direction.
var validPlacementValues = [ 'top', 'right', 'bottom' ];
var placement = COC.Tooltip.tooltip.placement;
// Check if custom placement is specified.
if( tooltipButton.is( '[data-placement]' ) ) {
var customPlacement = tooltipButton.attr( 'data-placement' );
// Validate
if( $.inArray( customPlacement, validPlacementValues ) >= 0 ) {
placement = customPlacement;
}
else {
// Throw error for invalid placement.
console.error( 'Invalid data-placement: \'' + placement + '\'. Must be one of: [' + validPlacementValues + ']. Defaulting to: ' + placement + '.' );
}
}
// Create the Popper
var popper = new Popper(tooltipButton, tooltip, {
placement: placement,
modifiers: {
flip: {
// behavior: ['left', 'right', 'top', 'bottom']
// MC.2018.11.13: The left placement is currently disabled,
// as it is not a preferred direction.
behavior: [ 'right', 'top', 'bottom' ]
}
},
onCreate: function() {
// MC.2018.11.13: There is a small positioning problem for the
// tooltips with an original placement of left or right.
// If there is no room to display the tooltip at the specified
// placement, the placement will then go to 'top', however the
// placement was slightly off. Calling popper.update will
// recalculate and apply the correct positioning.
popper.update();
}
});
// Show the tooltip.
tooltip.addClass( COC.Tooltip.tooltip.class.visible );
// Create escape key listener.
COC.Tooltip.documentKeyDown( tooltip );
// Create document click listener.
COC.Tooltip.documentClick( tooltip );
},// End of showTooltip()
/**
* hideTooltip()
* Hide the specified tooltip.
*/
hideTooltip: function( tooltip ) {
// Clear tooltip text.
tooltip.find( COC.Tooltip.selector.inner ).text( '' );
// Hide the tooltip and reset the data-trigger attr.
tooltip.removeClass( COC.Tooltip.tooltip.class.visible );
tooltip.attr( COC.Tooltip.data.trigger.attr, '' );
},
/**
* generateUID()
* Returns a unique id.
* @param prefix - string - Prefix for the ID
*/
generateUID: function( prefix ) {
// Generate a random ID.
var id = prefix + Math.floor(Math.random() * 100000);
// Confirm that the ID does not currently exist in the DOM.
if( $( '#' + id ).length === 0 ) {
// ID does not exist already, use it.
return id;
}
else {
// ID already exists, try again.
COC.Tooltip.generateUID( prefix );
}
}
};// End of COC.Tooltip
window.COC = COC;
$( document ).ready( function() {
COC.Tooltip.init();
});
}( jQuery, window, document ));