Javascript optimization: repetition of a same function - javascript

I want to use a getElemendbyId function several times, just by passing the name of the ID as a variable.
I guess there is a more elegant way to do it than:
<div id="1" onclick="myFunction2()"></div>
<div id="2" onclick="myFunction3()"></div>
<div id="3" onclick="myFunction4()"></div>
<div id="4" onclick="myFunction5()"></div>
<script>
function myFunction2() { document.getElementById("2").innerHTML = "test2"; }
function myFunction3() { document.getElementById("3").innerHTML = "test3"; }
function myFunction4() { document.getElementById("4").innerHTML = "test4"; }
</script>
Thanks!

<div id="1" onclick="myFunction(2, 'test2')"></div>
<div id="2" onclick="myFunction(3, 'test3')"></div>
<div id="3" onclick="myFunction(4, 'test4')"></div>
<div id="4" onclick="myFunction(5, 'test5')"></div>
<script>
function myFunction(id, content) {
document.getElementById(id).innerHTML = content;
}
</script>

There are a couple of ways to go about this, but in all scenarios you really should not use inline event handling attributes (onclick). There are many reasons not to use this 20+ year old technique that just will not die the death it deserved to almost 10 years ago. Additionally, don't use .innerHTML to get/set values that don't contain any HTML as it is wasteful, in terms of performance and it opens up security holes in your application. Instead, use .textContent to get/set non-HTML values.
For each element to have its own handler:
Get all the elements that need a similar handler into an array
Loop over the array
Assign a handler to the current array element
// Get all the elements that need the same handler into an Array
let divs = Array.prototype.slice.call(document.querySelectorAll("div"));
// Iterate the array
divs.forEach(function(div){
// Set up the handler
div.addEventListener("click", function(){
div.textContent = "test" + div.id;
});
});
<div id="1">click me</div>
<div id="2">click me</div>
<div id="3">click me</div>
<div id="4">click me</div>
To set up just one handler and use event delegation:
Assign a common handler to an ancestor of all the elements in question
In the handler, act upon the specific element that triggered the event.
// Set up an event handler on the container element
document.querySelector(".parent").addEventListener("click", function(event){
// Act upon the target of the event (the element that triggered the
// event in the first place).
event.target.textContent = "test" + event.target.id;
});
<div class="parent">
<div id="1">click me</div>
<div id="2">click me</div>
<div id="3">click me</div>
<div id="4">click me</div>
</div>

A more elegant solution than inline on<...> event handlers is to call addEventListener on the parent element (read about event delegation here). Once the listener is registered you can use the event argument to determine what target the user has clicked and what action to be taken, if any.
For example, in the scenario below we evaluate the event to determine if one of our <div> elements were clicked - if so, call myFunction with the appropriate data passed in:
document.addEventListener('click', handleClick)
function handleClick(event) {
if (event.target.tagName === 'DIV') {
myFunction(event.target);
}
}
function myFunction(el) {
el.innerHTML = `test${el.id}`;
}
<div id="1">Click Me</div>
<div id="2">Click Me</div>
<div id="3">Click Me</div>
<div id="4">Click Me</div>

You can use the getElementById and addEventListener functions. It looks like this
[1, 2, 3, 4].forEach(id => {
document.getElementById(id).addEventListener('click', () => {
const el = document.getElementById(id + 1);
if (el) el.innerHTML = `test${id+1}`;
});
});
<div id='1'>a</div>
<div id='2'>b</div>
<div id='3'>c</div>
<div id='4'>d</div>

Here is a solution without using Ids
function myFunction(el, content) {
el.innerHTML = content;
}
<div onclick="myFunction(this, 'test2')">Click Me</div>
<div onclick="myFunction(this, 'test3')">Click Me</div>
<div onclick="myFunction(this, 'test4')">Click Me</div>
<div onclick="myFunction(this, 'test5')">Click Me</div>

Event Delegation
Event delegation is a pattern that optimizes event handling by registering an ancestor element of a group of descendant elements. By controlling what is ignored and what is triggered by an event, you can have a single element (ancestor in demo is <ul>/ event.currentTarget) listen for an event (click event in demo) for all of its descendant elements (all of the <li> in demo event.target).
Demo
Details are commented in demo
// Reference an ancestor element
var list = document.querySelector('ul');
// Register click event on ancestor element
list.onclick = clicker;
// Callback function pass the Event Object
function clicker(event) {
// Reference the clicked element (<li>...)
var clicked = event.target;
// Reference the element registered to the event (<ul>)
var ancestor = event.currentTarget;
// if the clicked element is NOT the registered element
if (clicked !== ancestor) {
// if the clicked element is an <li>...
if (clicked.matches('li')) {
// ...toggle the .on class
clicked.classList.toggle('on');
}
}
}
li {
cursor: pointer;
}
.on {
color: gold;
background: black;
}
<ul>
<li>ITEM 1</li>
<li>ITEM 2</li>
<li>ITEM 3</li>
<li>ITEM 4</li>
</ul>

Related

How to prevent child element executing onmousedown event

I've got the following markup on the page
<div id="box">
<div id="box_child_one"></div>
<div id="box_child_two"></div>
<div id="box_child_three"></div>
</div>
I need to trigger the onmousedown event on all elements inside the #box div so i've got this javascript code:
var element = "#box";
document.querySelector(element).onmousedown = function() {
alert("triggered");
};
However, I do not want onmousedown being triggered on the #box_child_three element.
How can I achieve this?
Thank you.
Check event.target to find out which element was actually clicked on.
var element = "#box";
document.querySelector(element).onmousedown = function(e) {
if (e.target.id !== "box_child_three") {
alert("triggered");
}
};
<div id="box">
<div id="box_child_one">one</div>
<div id="box_child_two">two</div>
<div id="box_child_three">three</div>
</div>
You need to stopPropagation for the event when element three is clicked so that it doesn't bubble up to the parent (box) element.
document.getElementById('box').addEventListener('click', () =>
alert('triggered')
);
document.getElementById('box_child_three').addEventListener('click', e =>
e.stopPropagation()
);
<div id="box">
<div id="box_child_one">one</div>
<div id="box_child_two">two</div>
<div id="box_child_three">three</div>
</div>
<div id="box">
<div id="box_child_one" trigger></div>
<div id="box_child_two" trigger></div>
<div id="box_child_three"></div>
</div>
<script>
document.querySelector('#box').onclick = function(e){
if(e.target.hasAttribute('trigger')){
alert('event fired')
}
}
</script>
I'd go for this. As you are now no longer relying on an id to carry this out making it more re-usable

Javascript- removeEventListener not applied to array, from previous addEventListener added with for loop

I've given a lot of thought and troubleshooting to this issue, but cannot see where the error is.
I have some code that adds a click event listener to a bunch of divs.
The event listener is added using a for loop, which is applied as expected.
Then, after the click events have been used, I want to remove the event listener, using another for loop, and surprisingly it does not work.
Here is my js code:
var divElems = document.querySelectorAll(".circle");
for(var i=0; i < divElems.length; i++) {
divElems[i].addEventListener("click", circleClicked(i), false);
}
... // perform actions with click
function circleClicked(i) {
return function(){
console.log("Clicked circle " + i);
};
}
...
function removeEvListener() {
for(var i=0; i < divElems.length; i++) {
divElems[i].removeEventListener("click", circleClicked(i), false);
}
}
That should be applied on this html:
<div class="container">
<div class="circle">Circle 1</div>
<div class="circle">Circle 2</div>
<div class="circle">Circle 3</div>
<div class="circle">Circle 4</div>
<div class="circle">Circle 5</div>
<div class="circle">Circle 6</div>
<div class="circle">Circle 7</div>
<div class="circle">Circle 8</div>
<div class="circle">Circle 9</div>
<div class="circle empty"></div>
<div class="circle empty"></div>
</div>
The removeEvListener function is applied as a callback within the code, as part of an object of options: {option1: "value1", option2: value2, onComplete: removeEvListener}
I tried using just the reference, and also with the parentheses removeEvListener() and does not work.
The function added to the event listener accepts an input that relates to the index, which is used to identify the actual div being clicked.
Please help!
That's because removeEventListener takes a reference to the event handler function. In your case the function circleClicked creates a new function every time it's invoked, therefore you can't pass it to the removeEventListener.
There are two solutions:
store the references to your event listeners in an array (ugly)
create some more generic event handlers. If you really need to access the index of an element you can store it in the data- attribute. Or calculate it on demand.
//Edit
divElems contains all clickable elements, so in your click handler you could just check the index of the clikced element. In order to be able to easily handle this you can convert NodeList divElems to an array
var divElementsArray = Array.prototype.slice.call(divElems);
function clickHandler(e) {
// e parameter is the click event object,
// it has currentTarget property which would be a reference to the div element
var index = divElementsArray.indexOf(e.currentTarget);
console.log("Clicked circle's index: " + index);
}

How to make a re-usable element with $(this) triggers the clicked element only

I'm trying to make my JS logic to use the $(this) so I can re-use my code in multiple elements in the same page so that they are each triggered individually.
this is my JS code:
$(".fab").on("click", function(){
$(this).toggleClass("clicked");
$(".screen2").addClass("active");
$(".box2 ,.box1 ,.box3").addClass("active");
});
$("#close").on("click", function(){
$(".screen2").removeClass("active");
$(".fab").removeClass("clicked");
$(".box1 ,.box2 ,.box3").removeClass("active");
});
My HTML:
<div class="app">
<div class="header"></div>
<div class="content">
<h1>Click the fab</h1>
</div>
<div class="fab"></div>
<div class="screen2">
<h1>new window</h1>
<div id="close"></div>
<div class="box1"></div>
<div class="box2"></div>
<div class="box3"></div>
</div>
</div>
how can I use properly the $(this) ? All I'm trying to do is to make that JS code re-usable for multiple elements in the same page so that it triggers ONLY for the element where I clicked the .fab button...
You also need to use $(this), .prev(), .next(), .find(), .closest() etc. to traverse the DOM to refer to the elements near the one you're working on, otherwise it will target all of the classes in your document.
$(".fab").on("click", function(){
$(this).toggleClass("clicked");
$(this).next(".screen2").addClass("active");
$(this).next(".screen2").find(".box2 ,.box1 ,.box3").addClass("active");
});
$("#close").on("click", function(){
$(this).closest(".screen2").removeClass("active");
$(this).closest(".screen2").prev(".fab").removeClass("clicked");
$(this).closest(".screen2").find(".box1 ,.box2 ,.box3").removeClass("active");
});
You can store the jQuery selectors in an array, iterate through it, and for each jQuery selector in the array, use jQuery's .each() to iterate through its inner elements.
In the following example, there may be multiple instances of class1 in the DOM, for example.
var arrElements = [$('#id1'), $('#id2'), $('.class1')];
for (var i = 0; i < arrElements.length; i++) {
arrElements[i].each(function() {
$(this).on('click', function() {
// do stuff (use $(this) to refer to the current object)
});
})
}

How could i target a div and it's child to handle a click?

I have a <div class="stock"></div>wrapped around :
<div class="stockAdd"></div>
<div class="stockRemove"></div>
<div class="stockInput"></div>
I want to prevent a click inside my .stock to trigger a function. For now i have the following :
if ($(event.target).is('.stockInput') || $(event.target).is('.stockAdd') || $(event.target).is('.stockRemove')) {
console.log("Ajout stock");
return
}
Isn't there a better way to select thos three divs ? The $(event.target).is('.stock') don't get the job done when i click my nested divs.
Thanks
If I understand you correctly, you want to catch click events on .stockAdd, .stockRemove, and .stockInput, but not on other elements within .stock itself, is that correct?
If so, a delegated event can take care of that without any need to manually check the event target:
$('.stock').on('click', '.stockAdd, .stockRemove, .stockInput', function() {
alert("Clicked");
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="stock">
<div class="stockAdd">stockAdd</div>
<div class="stockRemove">stockRemove</div>
<div class="stockInput">stockInput</div>
<div>No event</div>
</div>
I would strongly recommend against depending on event.target here; it's too fragile. Any HTML tags nested inside your desired targets would break things:
$('.stock').on('click', function(event) {
if (event.target.className=="stockAll") {
alert("clicked");
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="stock">
<div class="stockAll">
This <b> will not work if someone clicks in the bold area</b> but works outside
</div>
</div>
You can add a separate class to all of them like .stock-inner and then grab them all with $('.stock-inner') or you can use a $("div[class^='stock-inner']) - this will grab the parent .stock div...
Also, to reject a click event within the handler you're gunna want to use e.preventDefault() where e is the event object.
the reason it doesn't work well on nested divs is they pass the conditional if in your example, to make it stricter you could add div to selector:
if ($(event.target).is('div.stockInput') || $(event.target).is('div.stockAdd') || $(event.target).is('div.stockRemove'))
You can attach the event on .stock and then filter using the event.target.
HTML
<div class="stock" style="border: 10px solid #000;">
<div class="stockAdd">Add</div>
<div class="stockRemove">Remove</div>
<div class="stockInput">Input</div>
</div>
JavaScript
$('.stock').on('click', function(e) {
if( e.target.className !== 'stock' ) {
console.log(e.target.className);
}
});
jsfiddle

Mixing CSS & Javascript Eventhandlers

I'd like to be able to assign the same event handler to a number of different div elements. Each element will have a unique style.
I registered the eventhandler like so:
<script type="text/javascript">
function add_something() {
document.getElementById('manipulate').onclick = do_something;
}
function do_something(e) {
this.style.property = value;
}
window.onLoad = add_something;
</script>
So I can assign an event handler to an element like so:
<div id="manipulate"></div>
The problem is that I want to assign that handler to several elements, each with a unique style, and I want to use CSS to do it. but i'm not sure how to do that.
Eventually, I want something that looks like this:
<style>
#element1{...}
#element1{...}
#element1{...}
</style>
.....
<div id="element1" class="manipulate"></div>
<div id="element2" class="manipulate"></div>
<div id="element3" class="manipulate"></div>
Is this possible? Thanks
Event delegation?
HTML:
<div id="wrap">
<div id="element1" class="manipulate">First element</div>
<div id="element2" class="manipulate">Second element</div>
<div id="element3" class="manipulate">Third element</div>
</div>
JavaScript:
var wrap = document.getElementById('wrap');
wrap.onclick = function (e) {
var elem = e.target;
elem.style.color = 'red';
};
Live demo: http://jsfiddle.net/VLcPw/
You can select elements by class in modern browsers, or just use jQuery and know it works everywhere.
$(document).ready(function(){
$('.manipulate').click(function(){alert('Hello.')});
//or .hover/any other event.
});
jQuery events.
Perhaps a more relevant example?
$('.manipulate').click(function(){
$(this).addClass( 'whateverClass' );
//or
$(this).css('color', 'red');
});
Why are you not giving jquery a try?
With jquery you should just need this to bind the same click event to all corresponding elements:
$("div.manipulate").click(function(e) {
$(this).css(property, value);
});
property has to replaced with a valid css property, value with a correct value, of course.

Categories

Resources