What is the best way ( fastest / proper ) fashion to do event delegation in vanilla js?
For example if I had this in jQuery:
$('#main').on('click', '.focused', function(){
settingsPanel();
});
How can I translate that to vanilla js? Perhaps with .addEventListener()
The way I can think of doing this is:
document.getElementById('main').addEventListener('click', dothis);
function dothis(){
// now in jQuery
$(this).children().each(function(){
if($(this).is('.focused') settingsPanel();
});
}
But that seems inefficient especially if #main has many children.
Is this the proper way to do it then?
document.getElementById('main').addEventListener('click', doThis);
function doThis(event){
if($(event.target).is('.focused') || $(event.target).parents().is('.focused') settingsPanel();
}
Rather than mutating the built-in prototypes (which leads to fragile code and can often break things), just check if the clicked element has a .closest element which matches the selector you want. If it does, call the function you want to invoke. For example, to translate
$('#main').on('click', '.focused', function(){
settingsPanel();
});
out of jQuery, use:
document.querySelector('#main').addEventListener('click', (e) => {
if (e.target.closest('#main .focused')) {
settingsPanel();
}
});
Unless the inner selector may also exist as a parent element (which is probably pretty unusual), it's sufficient to pass the inner selector alone to .closest (eg, .closest('.focused')).
When using this sort of pattern, to keep things compact, I often put the main part of the code below an early return, eg:
document.querySelector('#main').addEventListener('click', (e) => {
if (!e.target.matches('.focused')) {
return;
}
// code of settingsPanel here, if it isn't too long
});
Live demo:
document.querySelector('#outer').addEventListener('click', (e) => {
if (!e.target.closest('#inner')) {
return;
}
console.log('vanilla');
});
$('#outer').on('click', '#inner', () => {
console.log('jQuery');
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="outer">
<div id="inner">
inner
<div id="nested">
nested
</div>
</div>
</div>
I've come up with a simple solution which seems to work rather well (legacy IE support notwithstanding). Here we extend the EventTarget's prototype to provide a delegateEventListener method which works using the following syntax:
EventTarget.delegateEventListener(string event, string toFind, function fn)
I've created a fairly complex fiddle to demonstrate it in action, where we delegate all events for the green elements. Stopping propagation continues to work and you can access what should be the event.currentTarget through this (as with jQuery).
Here is the solution in full:
(function(document, EventTarget) {
var elementProto = window.Element.prototype,
matchesFn = elementProto.matches;
/* Check various vendor-prefixed versions of Element.matches */
if(!matchesFn) {
['webkit', 'ms', 'moz'].some(function(prefix) {
var prefixedFn = prefix + 'MatchesSelector';
if(elementProto.hasOwnProperty(prefixedFn)) {
matchesFn = elementProto[prefixedFn];
return true;
}
});
}
/* Traverse DOM from event target up to parent, searching for selector */
function passedThrough(event, selector, stopAt) {
var currentNode = event.target;
while(true) {
if(matchesFn.call(currentNode, selector)) {
return currentNode;
}
else if(currentNode != stopAt && currentNode != document.body) {
currentNode = currentNode.parentNode;
}
else {
return false;
}
}
}
/* Extend the EventTarget prototype to add a delegateEventListener() event */
EventTarget.prototype.delegateEventListener = function(eName, toFind, fn) {
this.addEventListener(eName, function(event) {
var found = passedThrough(event, toFind, event.currentTarget);
if(found) {
// Execute the callback with the context set to the found element
// jQuery goes way further, it even has it's own event object
fn.call(found, event);
}
});
};
}(window.document, window.EventTarget || window.Element));
I have a similar solution to achieve event delegation.
It makes use of the Array-functions slice, reverse, filter and forEach.
slice converts the NodeList from the query into an array, which must be done before it is allowed to reverse the list.
reverse inverts the array (making the final traversion start as close to the event-target as possible.
filter checks which elements contain event.target.
forEach calls the provided handler for each element from the filtered result as long as the handler does not return false.
The function returns the created delegate function, which makes it possible to remove the listener later.
Note that the native event.stopPropagation() does not stop the traversing through validElements, because the bubbling phase has already traversed up to the delegating element.
function delegateEventListener(element, eventType, childSelector, handler) {
function delegate(event){
var bubble;
var validElements=[].slice.call(this.querySelectorAll(childSelector)).reverse().filter(function(matchedElement){
return matchedElement.contains(event.target);
});
validElements.forEach(function(validElement){
if(bubble===undefined||bubble!==false)bubble=handler.call(validElement,event);
});
}
element.addEventListener(eventType,delegate);
return delegate;
}
Although it is not recommended to extend native prototypes, this function can be added to the prototype for EventTarget (or Node in IE). When doing so, replace element with this within the function and remove the corresponding parameter ( EventTarget.prototype.delegateEventListener = function(eventType, childSelector, handler){...} ).
Delegated events
Event delegation is used when in need to execute a function when existent or dynamic elements (added to the DOM in the future) receive an Event.
The strategy is to assign to event listener to a known static parent and follow this rules:
use evt.target.closest(".dynamic") to get the desired dynamic child
use evt.currentTarget to get the #staticParent parent delegator
use evt.target to get the exact clicked Element (WARNING! This might also be a descendant element, not necessarily the .dynamic one)
Snippet sample:
document.querySelector("#staticParent").addEventListener("click", (evt) => {
const elChild = evt.target.closest(".dynamic");
if ( !elChild ) return; // do nothing.
console.log("Do something with elChild Element here");
});
Full example with dynamic elements:
// DOM utility functions:
const el = (sel, par) => (par || document).querySelector(sel);
const elNew = (tag, prop) => Object.assign(document.createElement(tag), prop);
// Delegated events
el("#staticParent").addEventListener("click", (evt) => {
const elDelegator = evt.currentTarget;
const elChild = evt.target.closest(".dynamicChild");
const elTarget = evt.target;
console.clear();
console.log(`Clicked:
currentTarget: ${elDelegator.tagName}
target.closest: ${elChild?.tagName}
target: ${elTarget.tagName}`)
if (!elChild) return; // Do nothing.
// Else, .dynamicChild is clicked! Do something:
console.log("Yey! .dynamicChild is clicked!")
});
// Insert child element dynamically
setTimeout(() => {
el("#staticParent").append(elNew("article", {
className: "dynamicChild",
innerHTML: `Click here!!! I'm added dynamically! <span>Some child icon</span>`
}))
}, 1500);
#staticParent {
border: 1px solid #aaa;
padding: 1rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.dynamicChild {
background: #eee;
padding: 1rem;
}
.dynamicChild span {
background: gold;
padding: 0.5rem;
}
<section id="staticParent">Click here or...</section>
Direct events
Alternatively, you could attach a click handler directly on the child - upon creation:
// DOM utility functions:
const el = (sel, par) => (par || document).querySelector(sel);
const elNew = (tag, prop) => Object.assign(document.createElement(tag), prop);
// Create new comment with Direct events:
const newComment = (text) => elNew("article", {
className: "dynamicChild",
title: "Click me!",
textContent: text,
onclick() {
console.log(`Clicked: ${this.textContent}`);
},
});
//
el("#add").addEventListener("click", () => {
el("#staticParent").append(newComment(Date.now()))
});
#staticParent {
border: 1px solid #aaa;
padding: 1rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.dynamicChild {
background: #eee;
padding: 0.5rem;
}
<section id="staticParent"></section>
<button type="button" id="add">Add new</button>
Resources:
Event.target
Element.closest()
Related
What is the best way ( fastest / proper ) fashion to do event delegation in vanilla js?
For example if I had this in jQuery:
$('#main').on('click', '.focused', function(){
settingsPanel();
});
How can I translate that to vanilla js? Perhaps with .addEventListener()
The way I can think of doing this is:
document.getElementById('main').addEventListener('click', dothis);
function dothis(){
// now in jQuery
$(this).children().each(function(){
if($(this).is('.focused') settingsPanel();
});
}
But that seems inefficient especially if #main has many children.
Is this the proper way to do it then?
document.getElementById('main').addEventListener('click', doThis);
function doThis(event){
if($(event.target).is('.focused') || $(event.target).parents().is('.focused') settingsPanel();
}
Rather than mutating the built-in prototypes (which leads to fragile code and can often break things), just check if the clicked element has a .closest element which matches the selector you want. If it does, call the function you want to invoke. For example, to translate
$('#main').on('click', '.focused', function(){
settingsPanel();
});
out of jQuery, use:
document.querySelector('#main').addEventListener('click', (e) => {
if (e.target.closest('#main .focused')) {
settingsPanel();
}
});
Unless the inner selector may also exist as a parent element (which is probably pretty unusual), it's sufficient to pass the inner selector alone to .closest (eg, .closest('.focused')).
When using this sort of pattern, to keep things compact, I often put the main part of the code below an early return, eg:
document.querySelector('#main').addEventListener('click', (e) => {
if (!e.target.matches('.focused')) {
return;
}
// code of settingsPanel here, if it isn't too long
});
Live demo:
document.querySelector('#outer').addEventListener('click', (e) => {
if (!e.target.closest('#inner')) {
return;
}
console.log('vanilla');
});
$('#outer').on('click', '#inner', () => {
console.log('jQuery');
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="outer">
<div id="inner">
inner
<div id="nested">
nested
</div>
</div>
</div>
I've come up with a simple solution which seems to work rather well (legacy IE support notwithstanding). Here we extend the EventTarget's prototype to provide a delegateEventListener method which works using the following syntax:
EventTarget.delegateEventListener(string event, string toFind, function fn)
I've created a fairly complex fiddle to demonstrate it in action, where we delegate all events for the green elements. Stopping propagation continues to work and you can access what should be the event.currentTarget through this (as with jQuery).
Here is the solution in full:
(function(document, EventTarget) {
var elementProto = window.Element.prototype,
matchesFn = elementProto.matches;
/* Check various vendor-prefixed versions of Element.matches */
if(!matchesFn) {
['webkit', 'ms', 'moz'].some(function(prefix) {
var prefixedFn = prefix + 'MatchesSelector';
if(elementProto.hasOwnProperty(prefixedFn)) {
matchesFn = elementProto[prefixedFn];
return true;
}
});
}
/* Traverse DOM from event target up to parent, searching for selector */
function passedThrough(event, selector, stopAt) {
var currentNode = event.target;
while(true) {
if(matchesFn.call(currentNode, selector)) {
return currentNode;
}
else if(currentNode != stopAt && currentNode != document.body) {
currentNode = currentNode.parentNode;
}
else {
return false;
}
}
}
/* Extend the EventTarget prototype to add a delegateEventListener() event */
EventTarget.prototype.delegateEventListener = function(eName, toFind, fn) {
this.addEventListener(eName, function(event) {
var found = passedThrough(event, toFind, event.currentTarget);
if(found) {
// Execute the callback with the context set to the found element
// jQuery goes way further, it even has it's own event object
fn.call(found, event);
}
});
};
}(window.document, window.EventTarget || window.Element));
I have a similar solution to achieve event delegation.
It makes use of the Array-functions slice, reverse, filter and forEach.
slice converts the NodeList from the query into an array, which must be done before it is allowed to reverse the list.
reverse inverts the array (making the final traversion start as close to the event-target as possible.
filter checks which elements contain event.target.
forEach calls the provided handler for each element from the filtered result as long as the handler does not return false.
The function returns the created delegate function, which makes it possible to remove the listener later.
Note that the native event.stopPropagation() does not stop the traversing through validElements, because the bubbling phase has already traversed up to the delegating element.
function delegateEventListener(element, eventType, childSelector, handler) {
function delegate(event){
var bubble;
var validElements=[].slice.call(this.querySelectorAll(childSelector)).reverse().filter(function(matchedElement){
return matchedElement.contains(event.target);
});
validElements.forEach(function(validElement){
if(bubble===undefined||bubble!==false)bubble=handler.call(validElement,event);
});
}
element.addEventListener(eventType,delegate);
return delegate;
}
Although it is not recommended to extend native prototypes, this function can be added to the prototype for EventTarget (or Node in IE). When doing so, replace element with this within the function and remove the corresponding parameter ( EventTarget.prototype.delegateEventListener = function(eventType, childSelector, handler){...} ).
Delegated events
Event delegation is used when in need to execute a function when existent or dynamic elements (added to the DOM in the future) receive an Event.
The strategy is to assign to event listener to a known static parent and follow this rules:
use evt.target.closest(".dynamic") to get the desired dynamic child
use evt.currentTarget to get the #staticParent parent delegator
use evt.target to get the exact clicked Element (WARNING! This might also be a descendant element, not necessarily the .dynamic one)
Snippet sample:
document.querySelector("#staticParent").addEventListener("click", (evt) => {
const elChild = evt.target.closest(".dynamic");
if ( !elChild ) return; // do nothing.
console.log("Do something with elChild Element here");
});
Full example with dynamic elements:
// DOM utility functions:
const el = (sel, par) => (par || document).querySelector(sel);
const elNew = (tag, prop) => Object.assign(document.createElement(tag), prop);
// Delegated events
el("#staticParent").addEventListener("click", (evt) => {
const elDelegator = evt.currentTarget;
const elChild = evt.target.closest(".dynamicChild");
const elTarget = evt.target;
console.clear();
console.log(`Clicked:
currentTarget: ${elDelegator.tagName}
target.closest: ${elChild?.tagName}
target: ${elTarget.tagName}`)
if (!elChild) return; // Do nothing.
// Else, .dynamicChild is clicked! Do something:
console.log("Yey! .dynamicChild is clicked!")
});
// Insert child element dynamically
setTimeout(() => {
el("#staticParent").append(elNew("article", {
className: "dynamicChild",
innerHTML: `Click here!!! I'm added dynamically! <span>Some child icon</span>`
}))
}, 1500);
#staticParent {
border: 1px solid #aaa;
padding: 1rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.dynamicChild {
background: #eee;
padding: 1rem;
}
.dynamicChild span {
background: gold;
padding: 0.5rem;
}
<section id="staticParent">Click here or...</section>
Direct events
Alternatively, you could attach a click handler directly on the child - upon creation:
// DOM utility functions:
const el = (sel, par) => (par || document).querySelector(sel);
const elNew = (tag, prop) => Object.assign(document.createElement(tag), prop);
// Create new comment with Direct events:
const newComment = (text) => elNew("article", {
className: "dynamicChild",
title: "Click me!",
textContent: text,
onclick() {
console.log(`Clicked: ${this.textContent}`);
},
});
//
el("#add").addEventListener("click", () => {
el("#staticParent").append(newComment(Date.now()))
});
#staticParent {
border: 1px solid #aaa;
padding: 1rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.dynamicChild {
background: #eee;
padding: 0.5rem;
}
<section id="staticParent"></section>
<button type="button" id="add">Add new</button>
Resources:
Event.target
Element.closest()
What is the best way ( fastest / proper ) fashion to do event delegation in vanilla js?
For example if I had this in jQuery:
$('#main').on('click', '.focused', function(){
settingsPanel();
});
How can I translate that to vanilla js? Perhaps with .addEventListener()
The way I can think of doing this is:
document.getElementById('main').addEventListener('click', dothis);
function dothis(){
// now in jQuery
$(this).children().each(function(){
if($(this).is('.focused') settingsPanel();
});
}
But that seems inefficient especially if #main has many children.
Is this the proper way to do it then?
document.getElementById('main').addEventListener('click', doThis);
function doThis(event){
if($(event.target).is('.focused') || $(event.target).parents().is('.focused') settingsPanel();
}
Rather than mutating the built-in prototypes (which leads to fragile code and can often break things), just check if the clicked element has a .closest element which matches the selector you want. If it does, call the function you want to invoke. For example, to translate
$('#main').on('click', '.focused', function(){
settingsPanel();
});
out of jQuery, use:
document.querySelector('#main').addEventListener('click', (e) => {
if (e.target.closest('#main .focused')) {
settingsPanel();
}
});
Unless the inner selector may also exist as a parent element (which is probably pretty unusual), it's sufficient to pass the inner selector alone to .closest (eg, .closest('.focused')).
When using this sort of pattern, to keep things compact, I often put the main part of the code below an early return, eg:
document.querySelector('#main').addEventListener('click', (e) => {
if (!e.target.matches('.focused')) {
return;
}
// code of settingsPanel here, if it isn't too long
});
Live demo:
document.querySelector('#outer').addEventListener('click', (e) => {
if (!e.target.closest('#inner')) {
return;
}
console.log('vanilla');
});
$('#outer').on('click', '#inner', () => {
console.log('jQuery');
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="outer">
<div id="inner">
inner
<div id="nested">
nested
</div>
</div>
</div>
I've come up with a simple solution which seems to work rather well (legacy IE support notwithstanding). Here we extend the EventTarget's prototype to provide a delegateEventListener method which works using the following syntax:
EventTarget.delegateEventListener(string event, string toFind, function fn)
I've created a fairly complex fiddle to demonstrate it in action, where we delegate all events for the green elements. Stopping propagation continues to work and you can access what should be the event.currentTarget through this (as with jQuery).
Here is the solution in full:
(function(document, EventTarget) {
var elementProto = window.Element.prototype,
matchesFn = elementProto.matches;
/* Check various vendor-prefixed versions of Element.matches */
if(!matchesFn) {
['webkit', 'ms', 'moz'].some(function(prefix) {
var prefixedFn = prefix + 'MatchesSelector';
if(elementProto.hasOwnProperty(prefixedFn)) {
matchesFn = elementProto[prefixedFn];
return true;
}
});
}
/* Traverse DOM from event target up to parent, searching for selector */
function passedThrough(event, selector, stopAt) {
var currentNode = event.target;
while(true) {
if(matchesFn.call(currentNode, selector)) {
return currentNode;
}
else if(currentNode != stopAt && currentNode != document.body) {
currentNode = currentNode.parentNode;
}
else {
return false;
}
}
}
/* Extend the EventTarget prototype to add a delegateEventListener() event */
EventTarget.prototype.delegateEventListener = function(eName, toFind, fn) {
this.addEventListener(eName, function(event) {
var found = passedThrough(event, toFind, event.currentTarget);
if(found) {
// Execute the callback with the context set to the found element
// jQuery goes way further, it even has it's own event object
fn.call(found, event);
}
});
};
}(window.document, window.EventTarget || window.Element));
I have a similar solution to achieve event delegation.
It makes use of the Array-functions slice, reverse, filter and forEach.
slice converts the NodeList from the query into an array, which must be done before it is allowed to reverse the list.
reverse inverts the array (making the final traversion start as close to the event-target as possible.
filter checks which elements contain event.target.
forEach calls the provided handler for each element from the filtered result as long as the handler does not return false.
The function returns the created delegate function, which makes it possible to remove the listener later.
Note that the native event.stopPropagation() does not stop the traversing through validElements, because the bubbling phase has already traversed up to the delegating element.
function delegateEventListener(element, eventType, childSelector, handler) {
function delegate(event){
var bubble;
var validElements=[].slice.call(this.querySelectorAll(childSelector)).reverse().filter(function(matchedElement){
return matchedElement.contains(event.target);
});
validElements.forEach(function(validElement){
if(bubble===undefined||bubble!==false)bubble=handler.call(validElement,event);
});
}
element.addEventListener(eventType,delegate);
return delegate;
}
Although it is not recommended to extend native prototypes, this function can be added to the prototype for EventTarget (or Node in IE). When doing so, replace element with this within the function and remove the corresponding parameter ( EventTarget.prototype.delegateEventListener = function(eventType, childSelector, handler){...} ).
Delegated events
Event delegation is used when in need to execute a function when existent or dynamic elements (added to the DOM in the future) receive an Event.
The strategy is to assign to event listener to a known static parent and follow this rules:
use evt.target.closest(".dynamic") to get the desired dynamic child
use evt.currentTarget to get the #staticParent parent delegator
use evt.target to get the exact clicked Element (WARNING! This might also be a descendant element, not necessarily the .dynamic one)
Snippet sample:
document.querySelector("#staticParent").addEventListener("click", (evt) => {
const elChild = evt.target.closest(".dynamic");
if ( !elChild ) return; // do nothing.
console.log("Do something with elChild Element here");
});
Full example with dynamic elements:
// DOM utility functions:
const el = (sel, par) => (par || document).querySelector(sel);
const elNew = (tag, prop) => Object.assign(document.createElement(tag), prop);
// Delegated events
el("#staticParent").addEventListener("click", (evt) => {
const elDelegator = evt.currentTarget;
const elChild = evt.target.closest(".dynamicChild");
const elTarget = evt.target;
console.clear();
console.log(`Clicked:
currentTarget: ${elDelegator.tagName}
target.closest: ${elChild?.tagName}
target: ${elTarget.tagName}`)
if (!elChild) return; // Do nothing.
// Else, .dynamicChild is clicked! Do something:
console.log("Yey! .dynamicChild is clicked!")
});
// Insert child element dynamically
setTimeout(() => {
el("#staticParent").append(elNew("article", {
className: "dynamicChild",
innerHTML: `Click here!!! I'm added dynamically! <span>Some child icon</span>`
}))
}, 1500);
#staticParent {
border: 1px solid #aaa;
padding: 1rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.dynamicChild {
background: #eee;
padding: 1rem;
}
.dynamicChild span {
background: gold;
padding: 0.5rem;
}
<section id="staticParent">Click here or...</section>
Direct events
Alternatively, you could attach a click handler directly on the child - upon creation:
// DOM utility functions:
const el = (sel, par) => (par || document).querySelector(sel);
const elNew = (tag, prop) => Object.assign(document.createElement(tag), prop);
// Create new comment with Direct events:
const newComment = (text) => elNew("article", {
className: "dynamicChild",
title: "Click me!",
textContent: text,
onclick() {
console.log(`Clicked: ${this.textContent}`);
},
});
//
el("#add").addEventListener("click", () => {
el("#staticParent").append(newComment(Date.now()))
});
#staticParent {
border: 1px solid #aaa;
padding: 1rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.dynamicChild {
background: #eee;
padding: 0.5rem;
}
<section id="staticParent"></section>
<button type="button" id="add">Add new</button>
Resources:
Event.target
Element.closest()
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!
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);
});
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();
}
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>
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))
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);
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
})
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(';')
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
});
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.
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
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>
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
}
})
*/
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.
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>
//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;
}
});
}
}
'onclick' in the html works for both touch and click event. Here's the example.
This mini javascript libary (1.3 KB) can do all these things
https://github.com/Norair1997/norjs/
nor.event(["#first"], ["touchstart", "click"], [doSomething, doSomething]);
I am trying to get my code onto a page where I am not allowed a lot of changes; the person hosting the page allows me a <script> tag & a <div>, that's it.
--- page ---
<head>
<script type='text/javascript' src='https://example.com/myJSfile.js'>
</head>
<body>
<div id='mydiv'></div>
</body>
------------
When the page loads, how do I turn mydiv into a button, when I can only customize myJSfile.js?
I cannot promise any typical libraries such as jQuery will be loaded for me,
the host's site does load CSS, but I don't know the structure of their styles. Maybe I will have to learn some of it at some point.
If my code needs to load jQuery, I first have to check that it isn't already loaded. How would you do that specifically that check?
If I need to load my own css then I will have to do so dynamically using myJSfile.js
myJSfile.js js file can be anything I need it to be. I have full control over it.
How would you go about this?
Please remember that, besides myJSfile.js, I am pretty much locked out of anything on the page except the script & div tags.
Use insertbefore() function to add the new element, then the remove() function, to remove the existing <div> element:
// Create a <li> node:
var newItemDocument.createElement("LI");
// Create a text node
var textnode = document.createTextNode("Water");
// Append the text to <li>:
newItemDocument.appendChild(textnode);
// Get the <ul> element to insert a new node:
var list = document.getElementById("myList");
// Insert <li> before the first child of <ul>:
list.insertBefore(newItemDocument, list.childNodes[0]);
While you've already accepted an answer, I thought I'd take a moment to try and offer an answer that might anticipate your future requirements of adding event-listeners to the element(s) you want to insert, and perhaps replacing multiple elements with a common element:
// using a named, rather than an anonymous, function in
// order that the same function can be reused multiple
// times:
function replaceElementWith(opts) {
// these are the default settings:
let settings = {
'replaceWhat': '#mydiv',
'replaceWith': 'button',
'text': 'this is a button',
'eventHandlers': null
};
// here we iterate over all the keys in the
// (user-supplied) opts Object to override
// the defaults; we do this by first retrieving
// the keys of the opts Object, which returns
// an Array of those keys, and iterating over
// that Array using Array.prototype.forEach():
Object.keys(opts).forEach(
// here we use an arrow function syntax, since
// don't need to work with an updated 'this'
// within the function.
// key : the current key of the Array of keys,
// here we update the current key of the
// settings Object (or add a new key to that
// Object) to the same value as held in the
// opts Object:
key => settings[key] = opts[key]
);
// in order to allow you to perform the same replacement
// on multiple elements, we use document.querySelectorAll()
// to retrieve all elements matching the supplied CSS
// selector, and then pass that Array-like NodeList to
// Array.from(), which converts an Array-like structure to
// an Array:
let targets = Array.from(
document.querySelectorAll(
// this is the CSS selector passed via the
// opts.replaceWhat value (here '#mydiv'):
settings.replaceWhat
)
),
// here we create a new element according to the value
// passed via the settings.replaceWith value:
newElem = document.createElement(
settings.replaceWith
),
// an empty, uninitialised variable to hold the cloned
// newElem element within the loop (later):
clone;
// we set the textContent of the created Element to be
// equal to the passed-in text, via the opts.text property:
newElem.textContent = settings.text;
// here we iterate over the Array of found elements that
// are to be replaced:
targets.forEach(
// again, using an Arrow function expression:
target => {
// here we clone the created-element, along with
// any child nodes:
clone = newElem.cloneNode(true);
// unfortunately Node.cloneNode() doesn't clone
// event-listeners, so we have to perform this
// step on every iteration, we first test to
// see if settings.eventHandlers is a truthy
// value (so not the default null):
if (settings.eventHandlers) {
// if there are eventHandlers, then we retrieve
// the keys of the settings.eventHandlers
// Object as above:
Object.keys(settings.eventHandlers).forEach(
// using Arrow function again;
// the keys of this object are the event-types
// we're listening for and the values are the
// functions to handle that event, so here
// we add the 'eventType' as the event,
// and the 'settings.eventHandlers[eventType]
// (which retrieves the function) as the handler:
eventType => clone.addEventListener(eventType, settings.eventHandlers[eventType])
)
}
// here we find the parentNode of the element to be
// replaced, and use Node.replaceChild() to add the
// new element ('clone') in place of the target element:
target.parentNode.replaceChild(clone, target)
})
}
// calling the function, passing in values:
replaceElementWith({
// CSS selector to identify the element(s) to be removed:
'replaceWhat': '#mydiv',
// the eventHandlers Object to define the
// created element's event-handling:
'eventHandlers': {
// in the form of:
// 'eventName' : eventHandler
'click': function(e) {
e.preventDefault();
document.location.hash = 'buttonClicked';
this.style.opacity = this.style.opacity == 0.5 ? 1 : 0.5;
},
'mouseenter': function(e) {
this.style.borderColor = '#f90';
},
'mouseleave': function(e) {
this.style.borderColor = 'limegreen';
}
}
});
function replaceElementWith(opts) {
let settings = {
'replaceWhat': '#mydiv',
'replaceWith': 'button',
'text': 'this is a button',
'eventHandlers': null
};
Object.keys(opts).forEach(
key => settings[key] = opts[key]
);
let targets = Array.from(document.querySelectorAll(settings.replaceWhat)),
newElem = document.createElement(settings.replaceWith),
clone;
newElem.textContent = settings.text;
targets.forEach(
target => {
clone = newElem.cloneNode(true, true);
if (settings.eventHandlers) {
Object.keys(settings.eventHandlers).forEach(
eventType => clone.addEventListener(eventType, settings.eventHandlers[eventType]);
)
}
target.parentNode.replaceChild(clone, target)
})
}
replaceElementWith({
'replaceWhat': '#mydiv',
'eventHandlers': {
'click': function(e) {
e.preventDefault();
document.location.hash = 'buttonClicked';
this.style.opacity = this.style.opacity == 0.5 ? 1 : 0.5;
},
'mouseenter': function(e) {
this.style.borderColor = '#f90';
},
'mouseleave': function(e) {
this.style.borderColor = 'limegreen';
}
}
});
div {
border: 2px solid #f90;
}
button {
border: 2px solid limegreen;
}
<div id='mydiv'></div>
JS Fiddle demo.
The below demo does exactly the same as above, but does so working with multiple elements to be replaced, and the only change made is to the function call:
replaceElementWith({
// changed this selector to select by class,
// rather than id (and added multiple elements
// to the HTML):
'replaceWhat': '.mydiv',
'eventHandlers': {
'click': function(e) {
e.preventDefault();
document.location.hash = 'buttonClicked';
this.style.opacity = this.style.opacity == 0.5 ? 1 : 0.5;
},
'mouseenter': function(e) {
this.style.borderColor = '#f90';
},
'mouseleave': function(e) {
this.style.borderColor = 'limegreen';
}
}
});
function replaceElementWith(opts) {
let settings = {
'replaceWhat': '#mydiv',
'replaceWith': 'button',
'text': 'this is a button',
'eventHandlers': null
};
Object.keys(opts).forEach(
key => settings[key] = opts[key]
);
let targets = Array.from(document.querySelectorAll(settings.replaceWhat)),
newElem = document.createElement(settings.replaceWith),
clone;
newElem.textContent = settings.text;
targets.forEach(
target => {
clone = newElem.cloneNode(true, true);
if (settings.eventHandlers) {
Object.keys(settings.eventHandlers).forEach(
eventType => clone.addEventListener(eventType, settings.eventHandlers[eventType]);
)
}
target.parentNode.replaceChild(clone, target)
})
}
replaceElementWith({
'replaceWhat': '.mydiv',
'eventHandlers': {
'click': function(e) {
e.preventDefault();
document.location.hash = 'buttonClicked';
this.style.opacity = this.style.opacity == 0.5 ? 1 : 0.5;
},
'mouseenter': function(e) {
this.style.borderColor = '#f90';
},
'mouseleave': function(e) {
this.style.borderColor = 'limegreen';
}
}
});
div {
border: 2px solid #f90;
}
button {
border: 2px solid limegreen;
}
<div id='mydiv'></div>
JS Fiddle demo.
References:
Array.from().
Array.prototype.forEach().
Arrow Functions.
document.querySelectorAll().
EventTarget.addEventListener().
Node.cloneNode().
Node.replaceChild().
Object.keys().
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!
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);
});
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();
}
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>
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))
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);
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
})
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(';')
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
});
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.
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
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>
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
}
})
*/
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.
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>
//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;
}
});
}
}
'onclick' in the html works for both touch and click event. Here's the example.
This mini javascript libary (1.3 KB) can do all these things
https://github.com/Norair1997/norjs/
nor.event(["#first"], ["touchstart", "click"], [doSomething, doSomething]);