Wherever possible it is best to use what is referred to as Event Delegation.
This allows you to attach a single listener to a root object and inspect the event for the element that triggered the event and handle appropriately.
I usually use the following pattern:
// THE FOLLOWING CLASS AND HANDLER NAMES ARE FOR ILLUSTRATION ONLY :)
// HTML ELEMENTS
<div class="common_root_element">
...
<div class="item_to_be_clicked" data-clickhandler="handler1"></div>
<div class="item_to_be_clicked" data-clickhandler="handler2"></div>
<div class="item_to_be_clicked" data-clickhandler="handler3"></div>
</div>
// A MAP OF HANDLERS WHICH CAN BE REFERRED TO
const click_handlers ={
'handler1': ( e ) => {},
'handler2': ( e ) => {},
'handler3': ( e ) => {}
}
// IN THIS EXAMPLE THERE IS ONLY ONE ELEMENT, SO WE WON'T BIND IN A LOOP
document.getSelectorByClassName( 'common_root_element' )[ 0 ].addEventListener( 'click', ( e ) => {
// GET THE NAME OF THE HANDLER FROM THE HTML ELEMENT
const handler_name = e.dataset.clickhandler;
// MAKE SURE IT'S A FUNCTION AND EXECUTE
( typeof click_handlers[ handler_name ] === 'function' ) &&
click_handlers[ handler_name ]( e );
});
This allows for one event binding (rather than one for each element). It also allows for all handlers to be grouped into a single object, which makes them easy to find and refer to.
Here is an actual example set from a section I am working on now. This is for an image viewer which displays the images in a thumbnail grid. These are the bindings the for base set of functionality.
const click_handlers = {
'close' : ( e, $target, $thumbNailContainer, IMAGES_CONFIG, options_object ) => {
},
'filter_rating' : ( e, $target, $thumbNailContainer, IMAGES_CONFIG, options_object ) => {
},
'goto_page' : ( e, $target, $thumbNailContainer, IMAGES_CONFIG, options_object ) => {
},
'image_click' : ( e, $target, $thumbNailContainer, IMAGES_CONFIG, options_object ) => {
},
'page_next' : ( e, $target, $thumbNailContainer, IMAGES_CONFIG, options_object ) => {
},
'page_previous' : ( e, $target, $thumbNailContainer, IMAGES_CONFIG, options_object ) => {
},
'reload_images' : ( e, $target, $thumbNailContainer, IMAGES_CONFIG, options_object ) => {
},
'save_rating' : ( e, $target, $thumbNailContainer, IMAGES_CONFIG, options_object ) => {
},
'tag_filter' : ( e, $target, $thumbNailContainer, IMAGES_CONFIG, options_object ) => {
},
'tag_filter_clear' : ( e, $target, $thumbNailContainer, IMAGES_CONFIG, options_object ) => {
},
'thumbnail_click' : ( e, $target, $thumbNailContainer, IMAGES_CONFIG, options_object ) => {
},
'toggle_exif' : ( e, $target, $thumbNailContainer, IMAGES_CONFIG, options_object ) => {
},
'toggle_order_tools' : ( e, $target, $thumbNailContainer, IMAGES_CONFIG, options_object ) => {
},
'toggle_visibility' : ( e, $target, $thumbNailContainer, IMAGES_CONFIG, options_object ) => {
}
};
// BIND THE CLICK EVENT TO THE CONTAINER
$thumbNailContainer
.on( 'click.thumbnails', ( e ) => {
const $target = $( e.target );
const click_handler_name = $target.data( 'clickhandler' );
e.stopImmediatePropagation();
( click_handler_name && click_handlers[ click_handler_name ] ) &&
click_handlers[ click_handler_name ]( e, $target, $thumbNailContainer, IMAGES_CONFIG, options_object );
});
I am using jQuery for my element selection/event handling/etc. Inside of the defined click handler I determine the handler to execute and then pass in a list of arguments which is a superset of arguments needed by all handlers - the individual handlers only use the arguments they need.
I like this setup for the organization it allows me to bring.