Search code examples
javascriptaddeventlistenertogglebutton

Re-add event listeners after removing--Javascript


<style type="text/css">
.hilite {
    color:green;
    font-size: 1.5em;
    font-weight:bold;
}
.autosize {width: auto; height: auto;}
    ul {background-color: lightblue; height: 24px;}
    li {background-color: pink; width: 300px; height: 24px; font-weight: bold; font-size: 1.5em;}   
</style>

The web page has highlighted text which the user will mouse over to get a tooltip defining some terminology. That part works and I learned how to do it with event delegation which re-uses the event listener so I only need one of each kind of listener.

I'm trying to use radio inputs to give the user a choice of whether to use mouseover or click. I already tried it with buttons but radios seem better adapted. When the user clicks Hover, he gets tooltips by mousing over. When the user clicks Click, he gets tooltips by clicking. I was looking at ways to control this choice and couldn't understand explanations of how to do it with custom data attributes https://toddmotto.com/stop-toggling-classes-with-js-use-behaviour-driven-dom-manipulation-with-data-states/'>(I don't have the first clue) so I decided I should first learn how to do it by removing event listeners, that should be easy, right?

I had my add event listeners nicely shelved in an object as methods when I had to tear it all down in order to use removeEventListeners, since the method values were anonymous functions. That was just for organization, but it had the added benefit of making it natural to stay out of the global scope. This put everything into global scope but my real problem is worse than that.

REAL PROBLEM: The code works to remove click when using mouseover, or to remove mouseover when using click, but once I remove one event type, the radio buttons don't turn it back on. Apparently I need a different way to re-add the event listeners but I don't know why or how. I don't know why this doesn't work, it seems straightforward and I haven't found one example of this happening to anyone else. This is important because I plan to adapt it to a media query for users with touch screens since they can't use hover, but I prefer a mouseover default if the user has a mouse.

    <div id='tooltipAncestorOff'>
    <div id='tooltipAncestorClick'>
    <div id='tooltipAncestorHover'>
        <p class='autosize'>Just <span class='hilite' id="onez">FIRST hilite</span></p>
        <p class='autosize'>Only <span class='hilite' id="twoz">SECOND hilite</span></p>
        <p class='autosize'>Only <span class='hilite' id="threez">THIRD hilite</span></p>
        <p class='autosize'>Only <span class='hilite' id="fourz">FOURTH hilite</span></p>
        <p class='autosize'>Only <span class='hilite' id="fivez">FIFTH hilite</span></p>
        <p class='autosize'>Only <span class='hilite' id="sixz">SIXTH hilite</span></p>
    </div>  </div>  </div>

    <ul>
        <li id='toolTip'>placeholder... solve: why do I need text here?</li>
    </ul>

<form name="chooseTooltips">
  <input type="radio" name="tooltips" value="hover" onclick = 'onHoverButton()' checked> HOVER TOOLTIPS</br>
  <input type="radio" name="tooltips" value="click" onclick = 'onClickButton()'> CLICK TOOLTIPS</br>
  <input type="radio" name="tooltips" value="none" onclick = 'offBothButtons()'> NO TOOLTIPS</br>
</form>

<script>

    var what = {
        onez: 'FIRST definition',
        twoz: 'SECOND definition',
        threez: 'THIRD definition',
        fourz: 'FOURTH definition',
        fivez: 'FIFTH definition',
        sixz: 'SIXTH definition'    
    };

    var how = {
        showHide: function (stile){
        var displ = document.getElementById('toolTip');
        displ.style.display = stile;
        }
    };

    //most of the script below was in an object but I had to move it to global scope when using removeEventListener

    var clickFunx = function(evt) {
            var clicked = evt.target.id;
            toolTip.firstChild.textContent = (what[clicked]);
            how.showHide('block');
            console.log('clicked');
        };  
    var findClickParent = document.getElementById('tooltipAncestorClick');
        findClickParent.addEventListener('click', clickFunx, false);

    function removeHoverFunx() {
        findHoverParent.removeEventListener('mouseover', hoverFunx, false);
    }   

    var hoverFunx = function(evt) {
        var mousedOver = evt.target.id;
        toolTip.firstChild.textContent = (what[mousedOver]);
        how.showHide('block');
        console.log('moused over');
        };
    var findHoverParent = document.getElementById('tooltipAncestorHover');
        findHoverParent.addEventListener('mouseover', hoverFunx, false);    
    function removeClickFunx() {
        findClickParent.removeEventListener('click', clickFunx, false);
    };

    function hiliteOut() {
    var findOutParent = document.getElementById('tooltipAncestorHover');
        findOutParent.addEventListener('mouseout', function() {
            how.showHide('none');
            console.log('moused out');
        }, false);
    };

/********radio controls**********/  

function onHoverButton() {
    hoverFunx;
    hiliteOut();
    removeClickFunx();  
};

function onClickButton() {
    clickFunx;
    hiliteOut();
    removeHoverFunx();
};

function offBothButtons() {
    removeHoverFunx();
    removeClickFunx();
}

</script>

Solution

  • Instead of adding/removing the event every time - I registered both events (hover/click) and I only run the relevant function based on the current value of the radio-box:

    if (document.querySelector('[name="tooltips"]:checked').getAttribute('value') != 'click') {
        return;
    }
    

    Here is the fix to your code:

    var what = {
            onez: 'FIRST definition',
            twoz: 'SECOND definition',
            threez: 'THIRD definition',
            fourz: 'FOURTH definition',
            fivez: 'FIFTH definition',
            sixz: 'SIXTH definition'    
        };
    
        var how = {
            showHide: function (stile){
            var displ = document.getElementById('toolTip');
            displ.style.display = stile;
            }
        };
    
        //most of the script below was in an object but I had to move it to global scope when using removeEventListener
    
        var clickFunx = function(evt) {
                if (document.querySelector('[name="tooltips"]:checked').getAttribute('value') != 'click') {
                  return;
                }
                var clicked = evt.target.id;
                toolTip.firstChild.textContent = (what[clicked]);
                how.showHide('block');
                //console.log('clicked');
            };  
        var findClickParent = document.getElementById('tooltipAncestorClick');
            findClickParent.addEventListener('click', clickFunx, false);
    
        function removeHoverFunx() {
            findHoverParent.removeEventListener('mouseover', hoverFunx, false);
        }   
    
        var hoverFunx = function(evt) {
                if (document.querySelector('[name="tooltips"]:checked').getAttribute('value') != 'hover') {
                  return;
                }
            var mousedOver = evt.target.id;
            toolTip.firstChild.textContent = (what[mousedOver]);
            how.showHide('block');
            //console.log('moused over');
            };
        var findHoverParent = document.getElementById('tooltipAncestorHover');
            findHoverParent.addEventListener('mouseover', hoverFunx, false);    
        function removeClickFunx() {
            findClickParent.removeEventListener('click', clickFunx, false);
        };
    
        function hiliteOut() {
        var findOutParent = document.getElementById('tooltipAncestorHover');
            findOutParent.addEventListener('mouseout', function() {
                how.showHide('none');
                //console.log('moused out');
            }, false);
        };
    .hilite {
        color:green;
        font-size: 1.5em;
        font-weight:bold;
    }
    .autosize {width: auto; height: auto;}
        ul {background-color: lightblue; height: 24px;}
        li {background-color: pink; width: 300px; height: 24px; font-weight: bold; font-size: 1.5em;}
    <div id='tooltipAncestorOff'>
        <div id='tooltipAncestorClick'>
        <div id='tooltipAncestorHover'>
            <p class='autosize'>Just <span class='hilite' id="onez">FIRST hilite</span></p>
            <p class='autosize'>Only <span class='hilite' id="twoz">SECOND hilite</span></p>
            <p class='autosize'>Only <span class='hilite' id="threez">THIRD hilite</span></p>
            <p class='autosize'>Only <span class='hilite' id="fourz">FOURTH hilite</span></p>
            <p class='autosize'>Only <span class='hilite' id="fivez">FIFTH hilite</span></p>
            <p class='autosize'>Only <span class='hilite' id="sixz">SIXTH hilite</span></p>
        </div>  </div>  </div>
    
        <ul>
            <li id='toolTip'>placeholder... solve: why do I need text here?</li>
        </ul>
    
    <form name="chooseTooltips">
      <input type="radio" name="tooltips" value="hover" checked> HOVER TOOLTIPS<br />
      <input type="radio" name="tooltips" value="click"> CLICK TOOLTIPS<br />
      <input type="radio" name="tooltips" value="none"> NO TOOLTIPS<br />
    </form>