Search code examples
javascripttypeerroraddeventlistenerbodymovin

Using addEventListener on multiple elements, avoid TypeError when particular element not found


I'm using two simple addEventListener mouseenter and mouseleave functions respectively to play and stop animations (Bodymovin/SVG animations, though I suspect that fact is irrelevant).

So, the following works fine:

document.getElementById('animationDiv').addEventListener('mouseenter', function(){ 
    animation.play(); 
}) 

(The HTML couldn't be simpler: The relevant part is just an empty div placeholder filled by script - i.e., <div id="animationDiv"></div>.

I can place that in the same file as the one that operationalizes the animation code, or I can place it in a separate "trigger" file, with both files (and other others necessary to processing) loaded in the site footer.

The problem arises when I need to be able to set triggers for any of multiple similar animations that may or may not appear on a given page.

If only one of two animatable elements are present on a page, then one of two sets of triggers will throw an error. If the first of two such triggers is not present, then the second one will not be processed, meaning that the animation will fail. Or at least that's what it looks like to me is happening.

So, just to be clear, if I add the following two triggers for the same page, and the first of the following two elements is present, then the animation will play on mouseenter. If only the second is present, its animation won't be triggered, apparently because of the error thrown on the first.

document.getElementById('firstAnimationDiv').addEventListener('mouseenter', function(){ 
    firstAnimation.play(); 
})
document.getElementById('secondAnimationDiv').addEventListener('mouseenter', function(){ 
    secondAnimation.play(); 
})

At present I can work around the problem by creating multiple trigger files, one for each animation, and setting them to load only when I know that the animatable element will be present, but this approach would get increasingly inefficient when I am using multiple animations per page, on pages whose content may be altered.

I've looked at try/catch approaches and also at event delegation approaches, but so far they seem a bit complicated for handling this simple problem, if appropriate at all.

Is there an efficient and flexible standard method for preventing or properly handling an error for an element not found, in such a way that subsequent functions can still be processed? Or am I missing something else or somehow misreading the error and the function failure I've been encountering?

WHY I PICKED THE ANSWER THAT I DID (PLUS WORKING CODE)

I was easily able to make the simple, directly responsive answer by Baoo work.

I was unable to make the answers below by Patrick Roberts and Crazy Train work, though no doubt my undeveloped js skills are entirely at fault. When I have the time, or when the issue next comes up for me in a more complex implementation (possibly soon!), I'll take another look at their solutions, and see if I can either make them work or if I can formulate a better question with fully fledged coding examples to be worked through.

Finally, just to make things clear for people who might be looking for an answer on Bodymovin animations, and whose js is even weaker than mine, the following is working code, all added to the same single file in which a larger set of Bodymovin animations are constructed, relieving me of any need to create separate trigger files, and preventing TypeErrors and impaired functionality.

//There are three "lets_talk" animations that can play - "home," "snug," and "fixed"
//and three types of buttons needing enter and leave play and stop triggers
let home = document.getElementById('myBtn_bm_home');

if (home) home.addEventListener('mouseenter', function() { 
             lets_talk_home.play(); 
});
if (home) home.addEventListener('mouseleave', function() { 
             lets_talk_home.stop(); 
});

let snug = document.getElementById('myBtn_bm_snug');

if (snug) snug.addEventListener('mouseenter', function() { 
             lets_talk_snug.play(); 
});
if (snug) snug.addEventListener('mouseleave', function() { 
             lets_talk_snug.stop(); 
});

let fixed = document.getElementById('myBtn_bm_fixed');

if (fixed) fixed.addEventListener('mouseenter', function() { 
             lets_talk_fixed.play(); 
});
if (fixed) fixed.addEventListener('mouseleave', function() { 
             lets_talk_fixed.stop(); 
});

At typical piece of underlying HTML (it's generated by a PHP function taking into account other conditions, so not identical for each button), looks like this at the moment - although I'll be paring away the data-attribute and class, since I'm not currently using either. I provide it on the off-chance that someone sees something significant or useful there.

<div id="letsTalk" class="lets-talk">
    <a id="myBtn" href="#"><!-- a default-prevented link to a pop-up modal --> 
        <div class="bm-button" id="myBtn_bm_snug" data-animation="snug"></div><!-- "snug" (vs "fixed" or "home" is in both instances added by PHP -->
    </a>
</div>

Obviously, a more parsimonious and flexible answer could be - and probably should be - written. On that note, correctly combining both the play and stop listeners within a single conditional would be an obvious first step, but I'm too much of a js plodder even to get that right on a first or second try. Maybe later/next time!

Thanks again to everyone who provided an answer. I won't ask you to try to squeeze the working solution into your suggested framework - but I won't ask you not to either...


Solution

  • Just write your code so that it won't throw an error if the element isn't present, by simply checking if the element exists.

    let first = document.getElementById('firstAnimationDiv');
    if (first) first.addEventListener('mouseenter', function() {firstAnimation.play();});