Tooltip.js/tooltip.js
2024-10-03 15:44:01 -05:00

1087 lines
39 KiB
JavaScript

'use strict'
module.exports = {
/**
* Setup global options
* These can and will be overwriteen if a config object is passed into this.init()
*/
options: {
html: false,
placement: 'top',
container: 'body',
scrollContainer: window,
template: '<div class="tooltip" data-tooltip-target="tooltip"></div>',
removalDelay: 200,
tooltipOffset: 10,
windowPadding: {
top: 10,
right: 10,
bottom: 10,
left: 10,
},
},
/**
* initializeTooltips - Initialize function to bind events and set any global data
* @example
* tooltips.init()
* @param {object} (config) - Congifuration object that is used to overwrite the defaults in this.options
* @return {void}
*/
init: function initializeTooltips(config) {
var self = this
// Check to see if we should use document.body or document.documentElement
document.documentElement.scrollTop = 1
this.documentElement =
document.documentElement.scrollTop === 1
? document.documentElement
: document.body
// Test for browser support
var div = document.createElement('div')
div.innerHTML =
' <link/><table></table><a href="/a">a</a><input type="checkbox"/>'
// Make sure that link elements get serialized correctly by innerHTML
// This requires a wrapper element in IE
this.innerHTMLBug = !div.getElementsByTagName('link').length
div = undefined
// touch event testing
if (
'ontouchstart' in window ||
(window.DocumentTouch && document instanceof window.DocumentTouch)
) {
document.body.style.cursor = 'pointer'
}
// Wrap map from jquery.
this.map = {
legend: [1, '<fieldset>', '</fieldset>'],
tr: [2, '<table><tbody>', '</tbody></table>'],
col: [2, '<table><tbody></tbody><colgroup>', '</colgroup></table>'],
// for script/link/style tags to work in IE6-8, you have to wrap
// in a div with a non-whitespace character in front, ha!
defaultTag: this.innerHTMLBug
? [1, 'X<div>', '</div>']
: [0, '', ''],
}
this.map.td = this.map.th = [
3,
'<table><tbody><tr>',
'</tr></tbody></table>',
]
this.map.option = this.map.optgroup = [
1,
'<select multiple="multiple">',
'</select>',
]
this.map.thead =
this.map.tbody =
this.map.colgroup =
this.map.caption =
this.map.tfoot =
[1, '<table>', '</table>']
this.map.text =
this.map.circle =
this.map.ellipse =
this.map.line =
this.map.path =
this.map.polygon =
this.map.polyline =
this.map.rect =
[
1,
'<svg xmlns="http://www.w3.org/2000/svg" version="1.1">',
'</svg>',
]
// Copy over ininitialization options to options object
if (config instanceof Object) {
for (var option in config) {
if (
window.Object.hasOwnProperty.call(config, option) &&
window.Object.hasOwnProperty.call(this.options, option)
) {
// If it's a nested object, loop through that one too
if (
typeof config[option] === 'object' &&
!Array.isArray(config[option])
) {
for (var subkey in config[option]) {
if (
window.Object.hasOwnProperty.call(
config[option],
subkey
) &&
window.Object.hasOwnProperty.call(
this.options[option],
subkey
)
) {
this.options[option][subkey] =
config[option][subkey]
}
}
} else {
this.options[option] = config[option]
}
}
}
}
// Logic for handling a click event
function clickHandler(evt) {
if (!evt) {
evt = window.event
}
var trigger = evt.target || evt.srcElement
// If there's a tooltip open and it shouldn't close, don't close it _or_ open a new one
if (
self.currentTooltip &&
self.currentTooltip.getAttribute('data-autoclose') === 'false'
) {
return
// If there's already a tooltip open, close that one...
} else if (self.currentTooltip) {
// ...unless the user is clicking on the tooltip itself...
if (trigger === self.currentTooltip) {
return
// ...or if the user if clicking on the original trigger for that tooltip
} else if (trigger === self.currentTrigger) {
self.close(self.currentTooltip)
return
} else {
// loop through the child elements in the tooltip to see if one of them has been clicked
if (self.hasParent(trigger, self.currentTooltip)) {
return false
}
self.close(self.currentTooltip)
}
}
// If the element the user is clicking on isn't supposed to trigger a tooltip, bail
if (!self.hasClass(trigger, 'tooltip-click')) {
return
}
// Open the tooltip!
self.open(trigger)
}
// Logic for handling a mouseover event
function mouseoverHandler(evt) {
if (!evt) {
evt = window.event
}
var trigger = evt.target || evt.srcElement
// If the element the user is hovering over isn't supposed to trigger a tooltip, bail
if (!self.hasClass(trigger, 'tooltip-hover')) {
return
}
// If there's a tooltip open and it shouldn't close, don't close it _or_ open a new one
if (
self.currentTooltip &&
self.currentTooltip.getAttribute('data-autoclose') === 'false'
) {
return
// If there's already a tooltip open, close that one...
} else if (self.currentTooltip) {
// ...unless the user is hovering over the tooltip itself...
if (trigger === self.currentTooltip) {
return
} else {
// loop through the child elements in the tooltip to see if one of them has been clicked
if (self.hasParent(trigger, self.currentTooltip)) {
return false
}
self.close(self.currentTooltip)
}
}
// Logic for handling the mouseout event
function mouseoutHandler(moEvt) {
if (!moEvt) {
moEvt = window.event
}
var moTrigger = evt.target || evt.srcElement
if (self.hasClass(moTrigger)) {
return
}
// If the tooltip shouldn't autoclose, bail
if (
self.currentTooltip &&
self.currentTooltip.getAttribute('data-autoclose') ===
'false'
) {
return
}
self.close(self.currentTooltip)
// Remove self event to keep things clean
self.removeEventListener(trigger, 'mouseout', mouseoutHandler)
return
}
self.open(trigger)
// Add an event to remove the tooltip when the user moves their cursor away
self.addEventListener(trigger, 'mouseout', mouseoutHandler)
return
}
// Logic for handling a focus event
function focusHandler(evt) {
if (!evt) {
evt = window.event
}
var trigger = evt.target || evt.srcElement
// If the element the user is focusing on isn't supposed to trigger a tooltip, bail
if (!self.hasClass(trigger, 'tooltip-focus')) {
return
}
// If there's a tooltip open and it shouldn't close, don't close it _or_ open a new one
if (
self.currentTooltip &&
self.currentTooltip.getAttribute('data-autoclose') === 'false'
) {
return
// If there's already a tooltip open, close that one...
} else if (self.currentTooltip) {
self.close(self.currentTooltip)
}
// Logic for handling the blur event
function blurHandler() {
// If the tooltip shouldn't automatically close, bail
if (
self.currentTooltip &&
self.currentTooltip.getAttribute('data-autoclose') ===
'false'
) {
return
}
self.close(self.currentTooltip)
// Remove self event to keep things clean
self.removeEventListener(trigger, 'blur', blurHandler)
return
}
self.open(trigger)
// Add an event to remove the tooltip when the user blurs from the element
self.addEventListener(trigger, 'blur', blurHandler)
return
}
this.windowChangeHandler = function windowChangeHandler() {
if (self.currentTooltip && self.currentTrigger) {
self.positionTooltip(self.currentTooltip, self.currentTrigger)
}
return
}
// Add the global click handler
this.addEventListener(document.body, 'click', clickHandler)
// Add the global mouseover handler
this.addEventListener(document.body, 'mouseover', mouseoverHandler)
// Add the global focus handler
this.addEventListener(document.body, 'focus', focusHandler, true)
// If a tooltip is open and the user scrolls, isotip needs to keep up with the trigger
this.addEventListener(window, 'scroll', this.windowChangeHandler)
// If a tooltip is open and the user resizes the page, isotip needs to keep up with the trigger
this.addEventListener(window, 'resize', this.windowChangeHandler)
},
/**
* openTooltip - Main open function to prepare and insert the tooltip
* @example
* tooltip.open( document.body.querySelector( '#tooltip-trigger' ))
* @param {string|element} trigger - The element that serves as the trigger for the tooltip
* @param {object} (options) - An object that corresponds to the possible options using data-tooltip attributes
* @return {element} - Returns the tooltip that was inserted into the DOM
*/
open: function openTooltip(trigger, options) {
// We need a DOM element, so make it one if it isn't already
if (typeof trigger === 'string') {
trigger = document.body.querySelector(trigger)
}
// If no options are passed in, setup a blank object to prevent errors
if (!options) {
options = {}
}
// Setup tooltip variables, starting with the config object if there is one
var className =
options.className || trigger.getAttribute('data-tooltip-classname')
var content =
options.content || trigger.getAttribute('data-tooltip-content')
var title = options.title || trigger.getAttribute('data-tooltip-title')
var html = options.html || trigger.getAttribute('data-tooltip-html')
var placement =
options.placement || trigger.getAttribute('data-tooltip-placement')
var container =
options.container || trigger.getAttribute('data-tooltip-container')
var scrollContainer =
options.container ||
trigger.getAttribute('data-tooltip-scrollContainer')
var preExistingTooltip = document.querySelector('.tooltip')
var tooltip = this.createDOMElement(this.options.template)
var tooltipTitle
var tooltipContent
// If there isn't any content to be displayed, bail
if (!content) {
return
}
// If there's a tooltip open with autoClose set to false, don't open a new one
if (
this.currentTooltip &&
this.currentTooltip.getAttribute('data-autoclose') === 'false'
) {
return
}
tooltip.appendChild(
this.createDOMElement('<div class="tooltip-accent"></div>')
)
// If there should be an added class name, add it
if (className) {
this.addClass(tooltip, className)
}
// If there's a title to be displayed, create the title element
if (title) {
tooltipTitle = this.createDOMElement(
'<p class="tooltip-title">' + title + '</p>'
)
tooltip.appendChild(tooltipTitle)
}
// If the supplied string should be interpreted as html, make an element for it...
if ((this.options.html || html) && content) {
if (this.isElement(content)) {
tooltipContent = content
} else if (this.getTagName(content)) {
tooltipContent = this.createDOMElement(content)
} else {
tooltipContent = this.createDOMElement(
'<p class="tooltip-content">' + content + '</p>'
)
}
// ...or create a default p instead
} else {
tooltipContent = this.createDOMElement(
'<p class="tooltip-content">' + content + '</p>'
)
}
tooltip.appendChild(tooltipContent)
// If the a container was supplied and it's not also the body element, store that element
if (container && container !== this.options.container) {
if (typeof container === 'string') {
this.currentContainer = document.querySelector(container)
}
// If they initialized tooltips and set a different global container, store that element
} else {
this.currentContainer = document.querySelector(
this.options.container
)
}
// If a scrollContainer was supplied and it's also not the window element, store that element
if (
scrollContainer &&
scrollContainer !== this.options.scrollContainer
) {
if (typeof scrollContainer === 'string') {
this.currentScrollContainer =
document.querySelector(scrollContainer)
}
// If they initialized tooltips and incase they set a different global container, store that element
} else {
this.currentScrollContainer = this.options.scrollContainer
}
// If autoClose is set to false, add an attribute for the event handler to look for
if (
options.autoClose === false ||
trigger.getAttribute('data-tooltip-autoclose') === 'false'
) {
tooltip.setAttribute('data-autoclose', 'false')
}
if (preExistingTooltip) {
this.currentTooltip = preExistingTooltip.parentNode.insertBefore(
tooltip,
preExistingTooltip
)
} else {
this.currentTooltip = this.currentContainer.appendChild(tooltip)
}
this.currentTrigger = trigger
// Position the tooltip on the page
this.positionTooltip(
this.currentTooltip,
this.currentTrigger,
placement
)
// If a tooltip is open and the user scrolls, isotip needs to keep up with the trigger
if (this.currentScrollContainer !== window) {
this.addEventListener(
this.currentScrollContainer,
'scroll',
this.windowChangeHandler
)
}
return this.currentTooltip
},
/**
* closeTooltip - Main close function to close a specific tooltip
* @example
* tooltip.close( document.body.querySelector( '.tooltip' ))
* @param {string|element} tooltip - The tooltip that needs to be closed
* @return {void}
*/
close: function closeTooltip(tooltip) {
// We need a DOM element, so make it one if it isn't already
if (typeof tooltip === 'string') {
tooltip = document.body.querySelector(tooltip)
}
if (this.currentScrollContainer !== window) {
this.removeEventListener(
this.currentScrollContainer,
'scroll',
this.windowChangeHandler
)
}
this.removeClass(tooltip, 'visible')
this.currentTooltip = null
this.currentTrigger = null
this.currentScrollContainer = null
// We should assume that there will be some sort of tooltip animation with CSS or JS
// So we can only remove the element after a certain period of time
window.setTimeout(function removeElementFromDOM() {
if (
tooltip &&
tooltip instanceof window.Element &&
tooltip.parentNode
) {
tooltip.parentNode.removeChild(tooltip)
} else {
tooltip = document.body.querySelector('.tooltip')
if (tooltip && tooltip.parentNode) {
tooltip.parentNode.removeChild(tooltip)
}
}
}, this.options.removalDelay)
},
/**
* positionTooltip - Logic for positioning the tooltip on the page
* @example
* this.positionTooltip( this.currentTooltip, this.currentTrigger, 'top' )
* @param {string|element} tooltip - The tooltip that needs to be positioned
* @param {string|element} trigger - The element that triggered the tooltip
* @param {string} (placement) - The position that the tooltip needs to be placed in in relation to the trigger
* @return {element} - Returns the tooltip that was positioned
* @api private
*/
positionTooltip: function positionTooltip(tooltip, trigger, placement) {
if (typeof tooltip === 'string') {
tooltip = document.body.querySelector(tooltip)
}
if (typeof trigger === 'string') {
trigger = document.body.querySelector(trigger)
}
// Since we support this being done on scroll, we need a way to get the placement if it isn't specified
if (!placement) {
placement =
trigger.getAttribute('data-tooltip-placement') ||
this.options.placement
}
var self = this
var tooltipAccent = tooltip.querySelector('.tooltip-accent')
var triggerPosition = trigger.getBoundingClientRect()
var triggerWidth = Math.floor(triggerPosition.width)
var triggerHeight = Math.floor(triggerPosition.height)
var triggerX = triggerPosition.left
var triggerY = triggerPosition.top
var windowTop = this.options.windowPadding.top
var windowRight =
(window.innerWidth ||
document.documentElement.clientWidth ||
document.body.clientWidth) - this.options.windowPadding.right
var windowBottom =
(window.innerHeight ||
document.documentElement.clientHeight ||
document.body.clientHeight) - this.options.windowPadding.bottom
var windowLeft = this.options.windowPadding.left
var containerTop
var containerRight
var containerBottom
var containerLeft
var tooltipX
var tooltipY
var tooltipWidth
var tooltipHeight
var tooltipRight
var tooltipBottom
var tooltipAccentWidth
var tooltipAccentHeight
if (this.currentScrollContainer.getBoundingClientRect) {
var scrollContainerPosition =
this.currentScrollContainer.getBoundingClientRect()
if (scrollContainerPosition.top >= 0) {
containerTop =
scrollContainerPosition.top + this.options.windowPadding.top
}
if (scrollContainerPosition.right <= windowRight) {
containerRight =
scrollContainerPosition.right -
this.options.windowPadding.right
}
if (scrollContainerPosition.bottom <= windowBottom) {
containerBottom =
scrollContainerPosition.bottom -
this.options.windowPadding.bottom
}
if (scrollContainerPosition.left >= windowLeft) {
containerLeft =
scrollContainerPosition.left +
this.options.windowPadding.left
}
}
/**
* We sometimes need to re-position the tooltip (I.E. switch from top to bottom)
* Which is why these are separate functions
*/
function positionTop() {
tooltipX = triggerX - tooltipWidth / 2 + triggerWidth / 2
tooltipY = triggerY - tooltipHeight - self.options.tooltipOffset
tooltipRight = tooltipX + tooltipWidth
self.addClass(tooltip, 'tooltip-top')
if (!tooltipAccentWidth) {
tooltipAccentWidth = parseInt(tooltipAccent.offsetWidth, 10)
}
if (!tooltipAccentHeight) {
tooltipAccentHeight = parseInt(tooltipAccent.offsetHeight, 10)
}
if (triggerY + triggerHeight <= containerTop) {
if (self.hasClass(tooltip, 'visible')) {
self.removeClass(tooltip, 'visible')
}
}
// If the tooltip extends beyond the right edge of the window...
if (tooltipRight > windowRight || tooltipRight > containerRight) {
tooltip.style.top = 'auto'
tooltip.style.bottom =
windowBottom +
self.options.windowPadding.bottom -
triggerY +
self.options.tooltipOffset +
'px'
tooltip.style.right = self.options.windowPadding.right + 'px'
tooltipAccent.style.left = 'auto'
tooltipAccent.style.right =
triggerWidth / 2 -
tooltipAccentWidth / 2 +
(windowRight - triggerX - triggerWidth) +
'px'
// ...or if the tooltip extends beyond the top of the window...
} else if (tooltipY < windowTop || tooltipY < containerTop) {
self.removeClass(tooltip, 'tooltip-top')
return positionBottom()
// ...or if the tooltip extends beyond the left edge of the window...
} else if (tooltipX < windowLeft || tooltipX < containerLeft) {
tooltip.style.top = 'auto'
tooltip.style.bottom =
windowBottom +
self.options.windowPadding.bottom -
triggerY +
self.options.tooltipOffset +
'px'
tooltip.style.left = self.options.windowPadding.left + 'px'
tooltipAccent.style.right = 'auto'
tooltipAccent.style.left =
triggerWidth / 2 -
tooltipAccentWidth / 2 +
(triggerX - windowLeft) +
'px'
// ...or it fits inside the window
} else {
tooltip.style.top = 'auto'
tooltip.style.bottom =
windowBottom +
self.options.windowPadding.bottom -
triggerY +
self.options.tooltipOffset +
'px'
tooltip.style.left = tooltipX + 'px'
tooltipAccent.style.top = ''
tooltipAccent.style.bottom = ''
tooltipAccent.style.right = ''
tooltipAccent.style.left =
tooltipWidth / 2 - tooltipAccentWidth / 2 + 'px'
}
}
function positionRight() {
tooltipX = triggerX + triggerWidth + self.options.tooltipOffset
tooltipY = triggerY - tooltipHeight / 2 + triggerHeight / 2
tooltipRight = tooltipX + tooltipWidth
self.addClass(tooltip, 'tooltip-right')
if (!tooltipAccentHeight) {
tooltipAccentHeight = parseInt(tooltipAccent.offsetHeight, 10)
}
// If the tooltip extends beyond the right edge of the screen...
if (tooltipRight > windowRight || tooltipRight > containerRight) {
self.removeClass(tooltip, 'tooltip-right')
return positionTop()
// ...or if it fits to the right of the trigger element
} else {
tooltip.style.top = tooltipY + 'px'
tooltip.style.left = tooltipX + 'px'
tooltipAccent.style.right = ''
tooltipAccent.style.bottom = ''
tooltipAccent.style.left = ''
tooltipAccent.style.top =
tooltipHeight / 2 - tooltipAccentHeight / 2 + 'px'
}
}
function positionBottom() {
tooltipX = triggerX - tooltipWidth / 2 + triggerWidth / 2
tooltipY = triggerY + triggerHeight + self.options.tooltipOffset
tooltipRight = tooltipX + tooltipWidth
tooltipBottom = tooltipY + tooltipHeight
self.addClass(tooltip, 'tooltip-bottom')
if (!tooltipAccentWidth) {
tooltipAccentWidth = parseInt(tooltipAccent.offsetWidth, 10)
}
if (triggerY >= containerBottom) {
if (self.hasClass(tooltip, 'visible')) {
self.removeClass(tooltip, 'visible')
}
}
// If the tooltip extends beyond the right edge of the window...
if (tooltipRight > windowRight || tooltipRight > containerRight) {
tooltip.style.top = tooltipY + 'px'
tooltip.style.right = self.options.windowPadding.right + 'px'
tooltipAccent.style.left = 'auto'
tooltipAccent.style.right =
triggerWidth / 2 -
tooltipAccentWidth / 2 +
(windowRight - triggerX - triggerWidth) +
'px'
// ...or if the tooltip extends beyond the top of the window...
} else if (
tooltipBottom > windowBottom ||
tooltipBottom > containerBottom
) {
self.removeClass(tooltip, 'tooltip-bottom')
return positionTop()
// ...or if the tooltip extends beyond the left edge of the window...
} else if (tooltipX < windowLeft || tooltipX < windowLeft) {
tooltip.style.top = tooltipY + 'px'
tooltip.style.left = self.options.windowPadding.left + 'px'
tooltipAccent.style.right = 'auto'
tooltipAccent.style.left =
triggerWidth / 2 -
tooltipAccentWidth / 2 +
(triggerX - windowLeft) +
'px'
// ...or it fits inside the window
} else {
tooltip.style.top = tooltipY + 'px'
tooltip.style.bottom = 'auto'
tooltip.style.left = tooltipX + 'px'
tooltipAccent.style.top = ''
tooltipAccent.style.bottom = ''
tooltipAccent.style.right = ''
tooltipAccent.style.left =
tooltipWidth / 2 - tooltipAccentWidth / 2 + 'px'
}
}
function positionLeft() {
tooltipX = triggerX - tooltipWidth - self.options.tooltipOffset
tooltipY = triggerY - tooltipHeight / 2 + triggerHeight / 2
self.addClass(tooltip, 'tooltip-left')
if (!tooltipAccentHeight) {
tooltipAccentHeight = parseInt(tooltipAccent.offsetHeight, 10)
}
// If the tooltip extends beyond the right edge of the screen...
if (tooltipX < windowLeft || tooltipX < containerLeft) {
self.removeClass(tooltip, 'tooltip-left')
return positionTop()
// ...or if it fits to the left of the trigger element
} else {
tooltip.style.top = tooltipY + 'px'
tooltip.style.left = tooltipX + 'px'
tooltipAccent.style.right = ''
tooltipAccent.style.bottom = ''
tooltipAccent.style.left = ''
tooltipAccent.style.top =
tooltipHeight / 2 - tooltipAccentHeight / 2 + 'px'
}
}
tooltip.style.position = 'fixed'
tooltipWidth = parseInt(tooltip.offsetWidth, 10)
tooltipHeight = parseInt(tooltip.offsetHeight, 10)
tooltipAccentWidth = parseInt(tooltipAccent.offsetWidth, 10)
tooltipAccentHeight = parseInt(tooltipAccent.offsetHeight, 10)
// clear any classes set before hand
self.removeClass(tooltip, 'tooltip-top')
self.removeClass(tooltip, 'tooltip-right')
self.removeClass(tooltip, 'tooltip-bottom')
self.removeClass(tooltip, 'tooltip-left')
// position the tooltip
if (placement === 'top') {
positionTop()
} else if (placement === 'right') {
positionRight()
} else if (placement === 'bottom') {
positionBottom()
} else if (placement === 'left') {
positionLeft()
}
// try and give the tooltip enough time to position itself
if (
!self.hasClass(tooltip, 'visible') &&
(containerTop === undefined ||
(triggerY + triggerHeight > containerTop &&
triggerY < containerBottom))
) {
window.setTimeout(function () {
self.addClass(tooltip, 'visible')
}, 50)
} else if (
triggerY + triggerHeight <= containerTop ||
triggerY >= containerBottom
) {
self.removeClass(tooltip, 'visible')
}
return tooltip
},
/**
* addEventListener - Small function to add an event listener. Should be compatible with IE8+
* @example
* this.addEventListener( document.body, 'click', this.open( this.currentTooltip ))
* @param {element} el - The element node that needs to have the event listener added
* @param {string} eventName - The event name (sans the "on")
* @param {function} handler - The function to be run when the event is triggered
* @return {element} - The element that had an event bound
* @api private
*/
addEventListener: function addEventListener(
el,
eventName,
handler,
useCapture
) {
if (!useCapture) {
useCapture = false
}
if (el.addEventListener) {
el.addEventListener(eventName, handler, useCapture)
return el
} else {
if (eventName === 'focus') {
eventName = 'focusin'
}
el.attachEvent('on' + eventName, function () {
handler.call(el)
})
return el
}
},
/**
* removeEventListener - Small function to remove and event listener. Should be compatible with IE8+
* @example
* this.removeEventListener( document.body, 'click', this.open( this.currentTooltip ))
* @param {element} el - The element node that needs to have the event listener removed
* @param {string} eventName - The event name (sans the "on")
* @param {function} handler - The function that was to be run when the event is triggered
* @return {element} - The element that had an event removed
* @api private
*/
removeEventListener: function removeEventListener(
el,
eventName,
handler,
useCapture
) {
if (!useCapture) {
useCapture = false
}
if (!el) {
return
}
if (el.removeEventListener) {
el.removeEventListener(eventName, handler, useCapture)
} else {
if (eventName === 'focus') {
eventName = 'focusin'
}
el.detachEvent('on' + eventName, function () {
handler.call(el)
})
}
return el
},
/**
* hasClass - Small function to see if an element has a specific class. Should be compatible with IE8+
* @example
* this.hasClass( this.currentTooltip, 'visible' )
* @param {element} el - The element to check the class existence on
* @param {string} className - The class to check for
* @return {boolean} - True or false depending on if the element has the class
* @api private
*/
hasClass: function hasClass(el, className) {
if (el.classList) {
return el.classList.contains(className)
} else {
return new RegExp('(^| )' + className + '( |$)', 'gi').test(
el.className
)
}
},
/**
* addClass - Small function to add a class to an element. Should be compatible with IE8+
* @example
* this.addClass( this.currentTooltip, 'visible' )
* @param {element} el - The element to add the class to
* @param {string} className - The class name to add to the element
* @return {element} - The element that had the class added to it
* @api private
*/
addClass: function addClass(el, className) {
if (el.classList) {
el.classList.add(className)
} else {
el.className += ' ' + className
}
return el
},
/**
* removeClass - Small function to remove a class from an element. Should be compatible with IE8+
* @example
* this.removeClass( this.currentTooltip, 'visible' )
* @param {element} el - The element to remove the class from
* @param {string} className - The class name to remove from the element
* @return {element} - The element that had the class removed from it
* @api private
*/
removeClass: function removeClass(el, className) {
if (el) {
if (el.classList) {
el.classList.remove(className)
} else {
el.className = el.className.replace(
new RegExp(
'(^|\\b)' + className.split(' ').join('|') + '(\\b|$)',
'gi'
),
' '
)
}
}
return el
},
/**
* setInnerText - Small function to set the inner text of an element. Should be compatible with IE8+
* @example
* this.setInnerText( this.currentTooltip, 'Hello world' )
* @param {element} el - The element to have the text inserted into
* @param {string} text - The text to insert into the element
* @return {element} - The element with the new inner text
* @api private
*/
setInnerText: function setInnerText(el, text) {
if (el.textContent !== undefined) {
el.textcontent = text
} else {
el.innerText = text
}
return el
},
/**
* getTagName - Small function to get the tag name of an html string.
* @example
* this.getTagName( '<div></div>' )
* @param {string} html - The string of html to check for the tag
* @return {object} - The object containing the tag of the root element in the html string as well as some other basic info
* @api private
*/
getTagName: function getTagName(html) {
return /<([\w:]+)/.exec(html)
},
/**
* isElement - Small function to determine if object is a DOM element.
* @example
* this.isElement( '<div></div>' ); # false
* @example
* this.isElement( this.createElement('div') ); # true
* @param {string} obj - The object to check
* @return {boolean} - Whether or not the object is a DOM element.
* @api private
*/
isElement: function getTagName(obj) {
return !!(obj && obj.nodeType === 1)
},
/**
* createDOMElement - Small function to transform an html string into an element
* @example
* this.createDOMElement( '<div class="new-element">Hello world</div>' )
* @param {string} html - The string of html to turn into html
* @return {element} - The element created from the string of html
* @api private
*/
createDOMElement: function createDOMElement(html) {
// Remove whitespace
html = html.replace(/^\s+|\s+$/g, '')
// Get the tag name and match it to this.map incase it needs more nodes
var templateTag = this.getTagName(html)[1]
var wrap = this.map[templateTag] || this.map.defaultTag
var depth = wrap[0]
var prefix = wrap[1]
var suffix = wrap[2]
var el = document.createElement('div')
el.innerHTML = prefix + html + suffix
while (depth--) {
el = el.lastChild
}
// Extract the fresh element
return el.removeChild(el.firstChild)
},
/**
* hasParent - Small element to find the closest parent to an element
* @example
* element.closest('.tooltip')
* @param {Element} el - The element to start with
* @param {Element} parent - The parent element to match against/search for
* @return {bool} - Whether or not the element has the parent
*/
hasParent: function hasParent(el, parent) {
if (!el || !parent) {
return false
}
var match = false
while (el.parentNode && !match) {
el = el.parentNode
if (el === parent) {
match = true
}
}
return match
},
}