javascriptdomevent-listenertooltipster

How do I (make tooltipster) take over an existing or future eventlistener in javascript?


I use tooltipster to make tooltips on my site.

When hovering elements they display the content in a bubble.

On touch devices however..

..the tooltips shows on a tap since there is no hover. But a tap like a click triggers links etc. I go around this by making tooltipster take over the href of the element and display it inside the tooltip on a ad hoc created element like "Click here for more detail".
That works without a problem.

Now what about js click events?

I have elements on the site without href or <a> made by js addeventlistener. This is sometimes done before tooltipster is activated in js and sometimes after and sometimes even with ajax.
So, just like taking over the click/tap by href before, how do I make tooltipster take over the eventlistener?

More genreal question

This is not necessarily tooltipster specific. I can do that part. The question is rather, how to transfer eventlisteners in the first place?


Do I set a certain class on these elements and put the eventlisteners in a js Map? If so, then how do I transfer it to an element inside the tooltip without losing its target?

Or maybe this is just wrong coding, like the wrong approach in the first place? Maybe the question is wrong and I need another approach?

All help really appreciated!

There is code below, but it's not necessarily relevant:

isTouch = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)
$('.tooltip' + (isTouch?':not(.ajx)':''))
.tooltipster({
    debug:true,
    animation: 'grow',
    interactive:true,
    repositionOnScroll:true,
    contentAsHTML : true,
    //trackOrigin:true,     //performance!
    delay: 200,
    trigger: 'custom',
    triggerOpen: {
        click: true,
        mouseenter: true,
        tap: true
    },
    triggerClose: {
        mouseleave: true,
        click: true,
        tap: true
    }
})  
/***** Don't open tooltips of parents  ****/
//click (parent events comes after child)
.has('.tooltip')
    .tooltipster('option' , 'functionBefore' ,  (inst, helper) => {     //console.log('functionBefore', helper)
        if (helper.event && !helper.event.target?.isEqualNode(helper.origin))
            return false;//console.log(helper.event)//.stop()
    })
//hover (child events comes after parent)
.children('.tooltip')
    .tooltipster('option' , 'functionBefore' ,  (inst, helper) => {     //console.log('functionBefore', helper)
        $(helper.origin).parents('.tooltip').tooltipster('close')
    })
    .tooltipster('option' , 'functionAfter' ,  (inst, helper) => {      //console.log('functionAfter', helper)
        //If hover out on or via parent 
        if (helper.event.relatedTarget?.classList.contains('tooltip'))
            $(helper.event.relatedTarget).tooltipster('open')
    })

Solution

  • The only solution I found was with capturing events and setting a class on the relevant html elements.
    Explanation in code comments.

    isTouch = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)
    $tooltippeds = $('.tooltip' + (isTouch?':not(.noTouchTt)':'') ) 
        .tooltipster({
        debug:true,
        animation: 'grow',
        interactive:true,
        repositionOnScroll:true,
        contentAsHTML : true,
        //trackOrigin:true,     //performance!
        delay: 200,
        trigger: 'custom',
        triggerOpen: {
            click: true,
            mouseenter: true,
            tap: true
        },
        triggerClose: {
            mouseleave: true,
            click: true,
            tap: true
        }
    })  
    //***Make touch not trigger events or follow links by capturing via the parent, and put the event in a new element in the tooltip
    if (isTouch) {
        //Capture via class tooltipMore except when an ancestor is an <a>, then via  parent of the latter
        $tooltippeds.parents('a').add($tooltippeds.not('a .tooltip').filter('[tooltipMore]')).parent().each((i,el) => {
            //Evade creating it twice since tooltip elements share parents
            if($(el).closest('[hasTouchClickPrevent]').length>0)    return
            el.setAttribute('hasTouchClickPrevent',true)
            //Prevent the click event on child except when..
            el.addEventListener('click',(e) => {    console.log('tooltip click parent > ', window.tooltipMoreClicked)
                    if( !$(e.target).closest('.tooltip').length ||      //..child without tooltip
                        e.target.classList.contains('noTouchTt') ||     //..specifically denied with class 'noTouchTt' (can still be child)
                        window.tooltipMoreClicked ||                    //..triggered from inside tooltip "more" element
                        e.target==e.currentTarget                       //..if the parent itself has a click event
                    ) return true
                    e.preventDefault()
                    e.stopImmediatePropagation()
            },   true)      //capture!
          })
          $tooltippeds.filter('[tooltipMore], a .tooltip')
            //Put the event on a new element inside the tooltip and trigger with original target when clicked
            .tooltipster('option', 'functionFormat', (instance, helper, content) => {   //console.log('functionFormatBefore', content)
                $or = $(helper.origin)
                $content = $.parseHTML( content + '<a class="more">' + ($or.attr('tooltipMore')??'Click for details') + '</a>' )
                $($content).filter('.more').on('click', e => {
                    window.tooltipMoreClicked=true
                    evt = new PointerEvent(e.originalEvent.type, e)//clone event to get ctrlKey etc
                    helper.origin.dispatchEvent(evt)    //jquery trigger() is NO solution since it doesn't bubble up for tooltips inside <a>
                    window.tooltipMoreClicked = false
                
                })
                return $content
            })
    

    The tag tooltipMore shouldn't be needed if javascript or jQuery had a function to detect if any elements have eventlisteners.