Best method to apply a single function to multiple HTML items using Javascript?

On my site a modal pops up when you click on a portfolio image. In order to close the modal I have 2 anchor tags that can do so. One at the top of the page and one at the bottom. Currently both have their own ID’s and I use 2 separate functions to give them both the same functionality. This does not seem to follow the DRY (Dont Repeat Yourself) principle of coding.

Is there a way to target the class “toggler” and apply an onclick to both anchors with a single function?
What is the best way to achieve this functionality?

HTML

 <a href="#" id="first" class="toggler">Closer One</a>
 <a href="#" id="second" class="toggler">Closer Two</a>

JS

var closeOne = document.getElementById("first");

closeOne.onclick = function() {
	test.classList.remove("open2");
	body.classList.remove("modalopen");
};

var closeTwo = document.getElementById("second");

closeTwo.onclick = function() {
	test.classList.remove("open2");
	body.classList.remove("modalopen");
};

http://www.w3schools.com/jsref/met_document_getelementsbyclassname.asp with this function you can select multiple items with the same class

I did try replacing the document.getElementById with document.getElementsByClassName(“class”);
Was unable to get this working but will try again.

Give them all the same class and use document.getElementsByClassName().

Note that the function returns an array so you’ll need to iterate through the array and attach listeners to each member.

So something like:

var arr = document.getElementsByClassname("some classes");

for (var k=0; k<arr.length; k++)
   arr[k].addEventListener(yourListener);

1 Like

ah ha! this is what I was missing. Thanks for the input. I wasn’t even sure what to search for. Now I can research this to understand it better.

Thanks!

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.

1 Like