108

So my dilemma is that I don't want to write the same code twice. Once for the click event and another for the touchstart event.

Here is the original code:

document.getElementById('first').addEventListener('touchstart', function(event) {
    do_something();
    });

document.getElementById('first').addEventListener('click', function(event) {
    do_something(); 
    });

How can I compact this? There HAS to be a simpler way!

3
  • 3
    ES2020?! I wish.
    – mix3d
    Commented Jan 22, 2019 at 18:32
  • 1
    Still not available in ES2022 ?
    – anjanesh
    Commented Mar 1, 2022 at 10:22
  • ES2022?! Still a wish. Commented Jul 30, 2022 at 2:20

18 Answers 18

242

I thought some might find this approach useful; it could be applied to any similarly repetitive code:

ES6

['click','ontouchstart'].forEach( evt => 
    element.addEventListener(evt, dosomething, false)
);

ES5

['click','ontouchstart'].forEach( function(evt) {
    element.addEventListener(evt, dosomething, false);
});
8
  • 1
    But how to target specific selectors ?
    – Adeerlike
    Commented Feb 12, 2018 at 17:25
  • 7
    you can use something like: document.querySelectorAll(selectors).forEach((element) => { ... }); Commented Feb 13, 2018 at 18:51
  • When I try to use this, the function I write where you have written "dosomething" fires upon loading the page. Why is that? Even though the full line is: burgerElement.addEventListener(event, alertFunction(), false) and 'burgerelement' is var burgerElement = document.querySelector('.burger'); Commented Jan 22, 2019 at 17:47
  • 2
    you are executing alertFunction(), and assigning the return value of that (which is undefined) to the event handler. don't execute the function (with () ), just pass the function name/reference to the eventListener function. Notice in my example, dosomething does not have () after it. Commented Feb 19, 2019 at 20:07
  • 1
    ^^^^ The above comment is really important, leaving off the () lets the function lay dormant in the event listener until the event occurs, then the eventlistener basically calls the function for you....I know I am rehashing the above, but really easy to pull your hair out over....
    – Sanspoof
    Commented Aug 4, 2022 at 15:10
46

You can just define a function and pass it. Anonymous functions are not special in any way, all functions can be passed around as values.

var elem = document.getElementById('first');

elem.addEventListener('touchstart', handler, false);
elem.addEventListener('click', handler, false);

function handler(event) {
    do_something();
}
1
  • Thank you so very much. I needed to have multiple Textarea's to do a live preview and your code allowed me to do HTML and CSS all in one shot. Thank you.
    – CodingEE
    Commented Mar 9, 2022 at 2:05
33

Maybe you can use a helper function like this:

// events and args should be of type Array
function addMultipleListeners(element,events,handler,useCapture,args){
  if (!(events instanceof Array)){
    throw 'addMultipleListeners: '+
          'please supply an array of eventstrings '+
          '(like ["click","mouseover"])';
  }
  //create a wrapper to be able to use additional arguments
  var handlerFn = function(e){
    handler.apply(this, args && args instanceof Array ? args : []);
  }
  for (var i=0;i<events.length;i+=1){
    element.addEventListener(events[i],handlerFn,useCapture);
  }
}

function handler(e) {
  // do things
};

// usage
addMultipleListeners(
    document.getElementById('first'),
    ['touchstart','click'],
    handler,
    false);

[Edit nov. 2020] This answer is pretty old. The way I solve this nowadays is by using an actions object where handlers are specified per event type, a data-attribute for an element to indicate which action should be executed on it and one generic document wide handler method (so event delegation).

const firstElemHandler = (elem, evt) =>
  elem.textContent = `You ${evt.type === "click" ? "clicked" : "touched"}!`;
const actions = {
  click: {
    firstElemHandler,
  },
  touchstart: {
    firstElemHandler,
  },
  mouseover: {
    firstElemHandler: elem => elem.textContent = "Now ... click me!",
    outerHandling: elem => {
      console.clear();
      console.log(`Hi from outerHandling, handle time ${
        new Date().toLocaleTimeString()}`);
    },
  }
};

Object.keys(actions).forEach(key => document.addEventListener(key, handle));

function handle(evt) {
  const origin = evt.target.closest("[data-action]");
  return origin &&
    actions[evt.type] &&
    actions[evt.type][origin.dataset.action] &&
    actions[evt.type][origin.dataset.action](origin, evt) ||
    true;
}
[data-action]:hover {
  cursor: pointer;
}
<div data-action="outerHandling">
  <div id="first" data-action="firstElemHandler">
    <b>Hover, click or tap</b>
  </div>
  this is handled too (on mouse over)
</div>

1
13

For large numbers of events this might help:

var element = document.getElementById("myId");
var myEvents = "click touchstart touchend".split(" ");
var handler = function (e) {
    do something
};

for (var i=0, len = myEvents.length; i < len; i++) {
    element.addEventListener(myEvents[i], handler, false);
}

Update 06/2017:

Now that new language features are more widely available you could simplify adding a limited list of events that share one listener.

const element = document.querySelector("#myId");

function handleEvent(e) {
    // do something
}
// I prefer string.split because it makes editing the event list slightly easier

"click touchstart touchend touchmove".split(" ")
    .map(name => element.addEventListener(name, handleEvent, false));

If you want to handle lots of events and have different requirements per listener you can also pass an object which most people tend to forget.

const el = document.querySelector("#myId");

const eventHandler = {
    // called for each event on this element
    handleEvent(evt) {
        switch (evt.type) {
            case "click":
            case "touchstart":
                // click and touchstart share click handler
                this.handleClick(e);
                break;
            case "touchend":
                this.handleTouchend(e);
                break;
            default:
                this.handleDefault(e);
        }
    },
    handleClick(e) {
        // do something
    },
    handleTouchend(e) {
        // do something different
    },
    handleDefault(e) {
        console.log("unhandled event: %s", e.type);
    }
}

el.addEventListener(eventHandler);

Update 05/2019:

const el = document.querySelector("#myId");

const eventHandler = {
    handlers: {
        click(e) {
            // do something
        },
        touchend(e) {
            // do something different
        },
        default(e) {
            console.log("unhandled event: %s", e.type);
        }
    },
    // called for each event on this element
    handleEvent(evt) {
        switch (evt.type) {
            case "click":
            case "touchstart":
                // click and touchstart share click handler
                this.handlers.click(e);
                break;
            case "touchend":
                this.handlers.touchend(e);
                break;
            default:
                this.handlers.default(e);
        }
    }
}

Object.keys(eventHandler.handlers)
    .map(eventName => el.addEventListener(eventName, eventHandler))
4
  • 1
    why string.split() when you can just have a string array at no computational cost?
    – mix3d
    Commented Jan 22, 2019 at 18:28
  • @Torsten, But don't you need a string as first argument to identify the event? If so it defeats the purpose
    – Pacerier
    Commented May 20, 2019 at 12:33
  • @mix3d You're right defining an array is better and is the way to go for production code. Splitting the string is useful in some cases however where you read lists of words from strings or need to change the strings frequently and want to save typing and reading few characters. Commented May 24, 2019 at 12:37
  • 1
    @Pacerier you're right, this bug got unnoticed for a long time. You can also define a a plain object instead of a switch and use it's keys to listen to the events. Removing event listeners also gets easier if you have an object because you don't need to keep track of the exact listener and just need to pass the object. Commented May 24, 2019 at 12:41
9

Unless your do_something function actually does something with any given arguments, you can just pass it as the event handler.

var first = document.getElementById('first');
first.addEventListener('touchstart', do_something, false);
first.addEventListener('click', do_something, false);
2
  • 2
    I just thought there might be a quicker way .. something like: ('touchstart, click', do_something, false)
    – coiso
    Commented Aug 7, 2012 at 12:27
  • 1
    @PedroEsperança Nope. I agree, it would be nice to be able to pass an array of strings like ['touchstart','click'] but there's no such thing. Commented Aug 7, 2012 at 21:45
4

Simplest solution for me was passing the code into a separate function and then calling that function in an event listener, works like a charm.

function somefunction() { ..code goes here ..}

variable.addEventListener('keyup', function() {
   somefunction(); // calling function on keyup event
})

variable.addEventListener('keydown', function() {
   somefunction(); //calling function on keydown event
})
4

I'm new at JavaScript coding, so forgive me if I'm wrong. I think you can create an object and the event handlers like this:

const myEvents = {
  click: clickOnce,
  dblclick: clickTwice,
};

function clickOnce() {
  console.log("Once");
}

function clickTwice() {
  console.log("Twice");
}

Object.keys(myEvents).forEach((key) => {
  const myButton = document.querySelector(".myButton")
  myButton.addEventListener(key, myEvents[key]);
});
<h1 class="myButton">Button</h1>

And then click on the element.

1
  • PeterJames thank you for the edit. I 'm sorry for my mistakes. Commented Aug 19, 2022 at 15:41
2

I have a small solution that attaches to the prototype

  EventTarget.prototype.addEventListeners = function(type, listener, options,extra) {
  let arr = type;
  if(typeof type == 'string'){
    let sp = type.split(/[\s,;]+/);
    arr = sp;   
  }
  for(let a of arr){
    this.addEventListener(a,listener,options,extra);
  }
};

Allows you to give it a string or Array. The string can be separated with a space(' '), a comma(',') OR a Semicolon(';')

1
  • generally, prototype overrides are bad practice (potential future conflict when the specs change), but a perfect function nonetheless
    – mix3d
    Commented Jan 22, 2019 at 18:29
2

I just made this function (intentionally minified):

((i,e,f)=>e.forEach(o=>i.addEventListener(o,f)))(element, events, handler)

Usage:

((i,e,f)=>e.forEach(o=>i.addEventListener(o,f)))(element, ['click', 'touchstart'], (event) => {
    // function body
});

The difference compared to other approaches is that the handling function is defined only once and then passed to every addEventListener.

EDIT:

Adding a non-minified version to make it more comprehensible. The minified version was meant just to be copy-pasted and used.

((element, event_names, handler) => {

    event_names.forEach( (event_name) => {
        element.addEventListener(event_name, handler)
    })

})(element, ['click', 'touchstart'], (event) => {

    // function body

});
2
  • 3
    Why minify when showing an example? It only decreases comprehension.
    – mix3d
    Commented Jan 22, 2019 at 18:31
  • 3
    Edited for comprehensibility. Thanks for the input.
    – Ian Pollak
    Commented Jan 24, 2019 at 17:42
2

What about something like this:

['focusout','keydown'].forEach( function(evt) {
        self.slave.addEventListener(evt, function(event) {
            // Here `this` is for the slave, i.e. `self.slave`
            if ((event.type === 'keydown' && event.which === 27) || event.type === 'focusout') {
                this.style.display = 'none';
                this.parentNode.querySelector('.master').style.display = '';
                this.parentNode.querySelector('.master').value = this.value;
                console.log('out');
            }
        }, false);
});

// The above is replacement of:
 /*   self.slave.addEventListener("focusout", function(event) { })
      self.slave.addEventListener("keydown", function(event) {
         if (event.which === 27) {  // Esc
            }
      })
*/
1
  • 1
    This was the most helpful for me, specially using ['focusout','keydown'].forEach( function(evt) at the start. Commented Mar 22, 2023 at 0:12
1

document.getElementById('first').addEventListener('touchstart',myFunction);

document.getElementById('first').addEventListener('click',myFunction);
    
function myFunction(e){
  e.preventDefault();e.stopPropagation()
  do_something();
}    

You should be using e.stopPropagation() because if not, your function will fired twice on mobile

1

This is my solution in which I deal with multiple events in my workflow.

let h2 = document.querySelector("h2");

function addMultipleEvents(eventsArray, targetElem, handler) {
        eventsArray.map(function(event) {
            targetElem.addEventListener(event, handler, false);
        }
    );
}
let counter = 0;
function countP() {
    counter++;
    h2.innerHTML = counter;
}

// magic starts over here...
addMultipleEvents(['click', 'mouseleave', 'mouseenter'], h2, countP);
<h1>MULTI EVENTS DEMO - If you click, move away or enter the mouse on the number, it counts...</h1>

<h2 style="text-align:center; font: bold 3em comic; cursor: pointer">0</h2>

1

You can simply do it iterating an Object. This can work with a single or multiple elements. This is an example:

const ELEMENTS = {'click': element1, ...};
for (const [key, value] of Object.entries(ELEMENTS)) {
    value.addEventListener(key, () => {
        do_something();
    });
}

When key is the type of event and value is the element when you are adding the event, so you can edit ELEMENTS adding your elements and the type of event.

0

Semi-related, but this is for initializing one unique event listener specific per element.

You can use the slider to show the values in realtime, or check the console. On the <input> element I have a attr tag called data-whatever, so you can customize that data if you want to.

sliders = document.querySelectorAll("input");
sliders.forEach(item=> {
  item.addEventListener('input', (e) => {
    console.log(`${item.getAttribute("data-whatever")} is this value: ${e.target.value}`);
    item.nextElementSibling.textContent = e.target.value;
  });
})
.wrapper {
  display: flex;
}
span {
  padding-right: 30px;
  margin-left: 5px;
}
* {
  font-size: 12px
}
<div class="wrapper">
  <input type="range" min="1" data-whatever="size" max="800" value="50" id="sliderSize">
  <em>50</em>
  <span>Size</span>
  <br>
  <input type="range" min="1" data-whatever="OriginY" max="800" value="50" id="sliderOriginY">
  <em>50</em>
  <span>OriginY</span>
  <br>
  <input type="range" min="1" data-whatever="OriginX" max="800" value="50" id="sliderOriginX">
  <em>50</em>
  <span>OriginX</span>
</div>

0
//catch volume update
var volEvents = "change,input";
var volEventsArr = volEvents.split(",");
for(var i = 0;i<volknob.length;i++) {
    for(var k=0;k<volEventsArr.length;k++) {
    volknob[i].addEventListener(volEventsArr[k], function() {
        var cfa = document.getElementsByClassName('watch_televised');
        for (var j = 0; j<cfa.length; j++) {
            cfa[j].volume = this.value / 100;
        }
    });
}
}
0

'onclick' in the html works for both touch and click event. Here's the example.

0

For those whom are looking for a jQuery solution, it could be done like this:

$('#selector').on('click touchstart', function() {
    //do something here 
});

Or:

$('#selector').on('click touchstart', myFunction);
-1

This mini javascript libary (1.3 KB) can do all these things

https://github.com/Norair1997/norjs/

nor.event(["#first"], ["touchstart", "click"], [doSomething, doSomething]);

Not the answer you're looking for? Browse other questions tagged or ask your own question.