Variable out of scope but in scope - javascript

I have a jquery function that adds an event listener to various elements to execute on click. For some reason, when I try to reference a global variable (daySelected) inside the function, it says it is undefined. Am I missing something?
var daySelected = false;
$('.deal-day').bind('click', function(e) {
console.log("Day Selected: " + daySelected);
if (!daySelected) {
if($(this).hasClass('selected-filter')) {
$(this).removeClass('selected-filter');
var daySelected = false;
} else {
$(this).addClass('selected-filter');
var daySelected = true;
}
}
});

You have multiple var daySelected.
var should be defined only once. Otherwise you're creating new scopes.
As a general fix you could improve the code like:
let daySelected = false; // Feel free to change this boolean
const $dealDay = $(".deal-day"); // Get all buttons with that class
const toggleDaySelectedButtons = () => $dealDay.toggleClass("selected-filter", daySelected);
$dealDay.on("click", function(e) {
daySelected = !daySelected; // Toggle boolean
toggleDaySelectedButtons(); // Handle buttons
});
toggleDaySelectedButtons(); // Do on DOM ready
.selected-filter {background: gold;}
<button type="button" class="deal-day">Filter deal day</button>
<hr>
<button type="button" class="deal-day">Filter deal day</button>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
I really hope you have only one .deal-day button - otherwise the above will not be ideal

Related

Button in JavaScript not active / undefined

I added an event listener to my button and assigned it a variable. However, it is not functioning. The console keeps showing undefined with no code issues at the same time which makes it complex to figure out.
This is the html markup:
<button value="button" id="myBtn">Click to See</button>
Here is the JavaScript declaration for button to call the function "action" on click:
var btn = document.getElementById("myBtn").addEventListener("click", action);
Yet the console shows I got no issues and still shows btn undefined. Don't know how to fix it.
html :
<div>
<h3>ZooMoji</h3>
<img src="moji1.jpg" id="pic1">
<img src="moji2.jpg" id="pic2">
<button value="button" id="myBtn">Click to See</button>
</div>
JS:
var swap1 = document.getElementById("pic1").src;
var swap2 = document.getElementById("pic2").src;
var btn = document.getElementById("myBtn").addEventListener("click", action);
function action() {
if (swap1.endsWith("moji1.jpg") == true) {
swap1.src = swap2;
}
}
It's because you're getting the value of the .addEventListner function and not the actual .getElementById.
Do this instead.
let btn = document.getElementById("myBtn");
btn.addEventListener("click", action);
And also scrap those var's they're old and should in 99.9% of cases be replaced by either const or let if you want it to be mutable.
Actually, the event listener will work fine, and the function action will be executed whenever you click the button, try this:
function action() {
if (swap1.endsWith("moji1.jpg") == true) {
swap1.src = swap2;
}
console.log("the click event is working fine!")
}
The reason why the btn variable is returning undefined is the event listener itself which returns a void, if you are using VS Code try to hover over the btn variable and you would see something like this:
var btn: void
So in order to get an HTMLElement from the btn variable just do that:
var btn = document.getElementById("myBtn"); // returns HTMLElement
btn.addEventListener("click", action); // returns void
function action() {
if (swap1.endsWith("moji1.jpg") == true) {
swap1.src = swap2;
}
}
For more details: EventTarget.addEventListener()

Callback Function does not work with for Loop

Why doesn't the callButtonTwo() function work on all buttons? Only button1 reacts to it?
document.querySelector("h1").addEventListener("click", function() {
alert("Working!");
})
var totalButton = document.querySelectorAll(".testSecond").length;
for (var i = 0; i < totalButton; i++) {
document.querySelectorAll(".testSecond")[i].addEventListener("click", function() {
var buttonNow = this.innerHTML;
callButtonOne(buttonNow);
callButtonTwo(buttonNow);
});
}
function callButtonOne() {
alert("I got clicked!");
};
function callButtonTwo() {
var changeRed = document.querySelector(".testSecond");
changeRed.classList.add("red");
setTimeout(function() {
changeRed.classList.remove("red");
}, 300);
};
.red {
background-color: red;
}
<h1 class="testFirst">Hello World!</h1>
<button class="testSecond button1" type="button" name="button">button1</button>
<button class="testSecond button2" type="button" name="button">button2</button>
<button class="testSecond button3" type="button" name="button">button3</button>
<button class="testSecond button4" type="button" name="button">button4</button>
<button class="testSecond button5" type="button" name="button">button5</button>
While, inside your click handler you have var buttonNow = this.innerHTML; which operates on that button, when you call callButtonTwo you say var changeRed = document.querySelector(".testSecond"); which operates on the first button (no matter which button you click).
You need to tell it which button you are dealing with (e.g. by passing this as an argument)
The problem is, that your querySelector in callButtonTwo just finds the first button because all of them have the same class .testSecond.
One possible solution is to add the reference of the button which should be colored red to the callButtonTwo function and use the reference to add the class name.
It would look like this:
function callButtonTwo(button) {
button.classList.add("red");
setTimeout(function() {
button.classList.remove("red");
}, 300);
};
And you would call it like this: callButtonTwo(this);
Here's a working example based on the code you provided.
Inside callButtonTwo you're querying .testSecond again which will always return the first element in the page with that class. If you want to handle each button differently you should pass the button element as a parameter to callButtonTwo, like so:
var totalButton = document.querySelectorAll(".testSecond").length;
for (var i = 0; i < totalButton; i++) {
document.querySelectorAll(".testSecond")[i].addEventListener("click", function() {
var buttonNow = this.innerHTML;
callButtonOne(buttonNow);
callButtonTwo(this); // 'this' in this case is the clicked button element
});
}
[...]
function callButtonTwo(button) {
button.classList.add("red");
setTimeout(function() {
button.classList.remove("red");
}, 300);
};
I would also consider to change the way you're iterating your elements in order to query the DOM less frequently:
// 'querySelectorAll' returns an array which can be directly iterated using its method 'forEach'
document.querySelectorAll(".testSecond").forEach(function(button) {
button.addEventListener("click", function() {
var buttonNow = this.innerHTML;
callButtonOne(buttonNow);
callButtonTwo(this);
});
});

Toggle Event Listeners

I am trying to make a function that would allow me to toggle eventListener of an element.
In the example below, I have three buttons: main, on and off. When I click on the on button, the main button becomes functional. After I click off button, the main button should not work anymore (but now it still does).
Now I can achieve a desired behavior by clicking on button for the second time, but I guess it's a bad coincidence and it's not supposed to work that way.
Maybe I should add that I would like to work this out without using jQuery or similar and it needs to be a function, because I am going to use it for a lot of buttons.
(I suspect something with scope causes the problem (clickHandler when calling the function to activate the button is not the same as the clickHandler when calling the function to disable the button), but I can't think of a way to test it.)
// buttons definitions, not important
var mainButton = document.querySelector("#mainButton");
var onButton = document.querySelector("#onButton");
var offButton = document.querySelector("#offButton");
// main function
var toggleButtons = function(toggleVal, button, element) {
var activateButton, clickHandler, disableButton;
// callback function for listener bellow
clickHandler = function() {
document.querySelector(element).classList.toggle("yellow");
};
activateButton = function() {
button.addEventListener("click", clickHandler);
};
disableButton = function() {
button.removeEventListener("click", clickHandler);
};
// when first argument is 1, make the button functional, otherwise disable its functionality
if (toggleVal === 1) {
activateButton();
} else {
disableButton();
}
};
// when onButton is clicked, call main function with arguments
// this works
onButton.addEventListener("click", function() {
toggleButtons(1, mainButton, "body");
});
// this fails to disable the button
offButton.addEventListener("click", function() {
toggleButtons(0, mainButton);
});
.yellow {
background-color: yellow;
}
<button type="button" id="mainButton">mainButton
</button>
<button type="button" id="onButton">onButton
</button>
<button type="button" id="offButton">offButton
</button>
<p>mainButton: toggles background color on click
</p>
<p>onButton: turns on mainButtons's functionality</p>
<p>offButton: supposed to turn off mainButton's functionality</p>
var mainButton = document.querySelector("#mainButton");
var onButton = document.querySelector("#onButton");
var offButon = document.querySelector("#offButton");
var element; // declare the element here and change it from toggleButtons when needed.
function clickHandler() {
document.querySelector(element).classList.toggle('yellow');
}
function activateButton(button) { // You missed this part
button.addEventListener('click', clickHandler);
}
function disableButton(button) { // You missed this part
button.removeEventListener('click', clickHandler);
}
function toggleButtons(value, button) {
if (value === 1) {
activateButton(button); // You missed this part
} else {
disableButton(button); // You missed this part
}
};
onButton.addEventListener('click', function() {
element = 'body'; // you can change it to some other element
toggleButtons(1, mainButton);
});
offButton.addEventListener('click', function() {
element = 'body'; // you can change it to some other element
toggleButtons(0, mainButton);
});
Below code helps to toggle between two functions from an eventListener:
var playmusic=false;
function playSound() {
const audio = document.querySelector(`audio[data-key="${event.keyCode}"]`)
audio.currentTime = 0
audio.play()
playmusic=true;
}
function stopSound() {
const audio = document.querySelector(`audio[data-key="${event.keyCode}"]`)
audio.pause()
playmusic=false;
}
window.addEventListener('keydown',
function(){playmusic?stopSound():playSound()} )

Stop function after certain number of clicks on a certain element

OK so I am making a reaction tester, and I have a function that makes shapes appear on screen, So what I want is some sort of function were after 5 clicks on a certain element it will end a function. Is there a way of doing that? sorry if its a dumb question, its because I am new to the whole coding...
Here you go
var clickHandler = (function (e) {
var count = 0;
return function () {
count += 1;
if (count > 5) {
return;
}
// do other stuff here
}
}());
aDiv.addEventListener('click', clickHandler, false);
You Can use static variable to count how many times the object has been clicked.
and here is how you can create static variable in javascript.
You can unbind the click event once the counter reaches 5. See the example below
function test(sender) {
sender.dataset.clicked++;
console.log("I've been clicked", sender.dataset.clicked);
if (+sender.dataset.clicked === 5) {
// unbind the event
sender.onclick = null;
}
return;
}
<div onclick="test(this);" data-clicked="0">click me</div>
You may use global variable which may remain counting on click function
<script>
var globalvar = 0;
onclickfunct()
{
globalvar += 1;
if(globalvar == 5)
{
//do my work
}
else
{
//give alert
}
}
</script>

Class methods as event handlers in JavaScript?

Is there a best-practice or common way in JavaScript to have class members as event handlers?
Consider the following simple example:
<head>
<script language="javascript" type="text/javascript">
ClickCounter = function(buttonId) {
this._clickCount = 0;
document.getElementById(buttonId).onclick = this.buttonClicked;
}
ClickCounter.prototype = {
buttonClicked: function() {
this._clickCount++;
alert('the button was clicked ' + this._clickCount + ' times');
}
}
</script>
</head>
<body>
<input type="button" id="btn1" value="Click me" />
<script language="javascript" type="text/javascript">
var btn1counter = new ClickCounter('btn1');
</script>
</body>
The event handler buttonClicked gets called, but the _clickCount member is inaccessible, or this points to some other object.
Any good tips/articles/resources about this kind of problems?
ClickCounter = function(buttonId) {
this._clickCount = 0;
var that = this;
document.getElementById(buttonId).onclick = function(){ that.buttonClicked() };
}
ClickCounter.prototype = {
buttonClicked: function() {
this._clickCount++;
alert('the button was clicked ' + this._clickCount + ' times');
}
}
EDIT almost 10 years later, with ES6, arrow functions and class properties
class ClickCounter {
count = 0;
constructor( buttonId ){
document.getElementById(buttonId)
.addEventListener( "click", this.buttonClicked );
}
buttonClicked = e => {
this.count += 1;
console.log(`clicked ${this.count} times`);
}
}
https://codepen.io/anon/pen/zaYvqq
I don't know why Function.prototype.bind wasn't mentioned here yet. So I'll just leave this here ;)
ClickCounter = function(buttonId) {
this._clickCount = 0;
document.getElementById(buttonId).onclick = this.buttonClicked.bind(this);
}
ClickCounter.prototype = {
buttonClicked: function() {
this._clickCount++;
alert('the button was clicked ' + this._clickCount + ' times');
}
}
A function attached directly to the onclick property will have the execution context's this property pointing at the element.
When you need to an element event to run against a specific instance of an object (a la a delegate in .NET) then you'll need a closure:-
function MyClass() {this.count = 0;}
MyClass.prototype.onclickHandler = function(target)
{
// use target when you need values from the object that had the handler attached
this.count++;
}
MyClass.prototype.attachOnclick = function(elem)
{
var self = this;
elem.onclick = function() {self.onclickHandler(this); }
elem = null; //prevents memleak
}
var o = new MyClass();
o.attachOnclick(document.getElementById('divThing'))
You can use fat-arrow syntax, which binds to the lexical scope of the function
function doIt() {
this.f = () => {
console.log("f called ok");
this.g();
}
this.g = () => {
console.log("g called ok");
}
}
After that you can try
var n = new doIt();
setTimeout(n.f,1000);
You can try it on babel or if your browser supports ES6 on jsFiddle.
Unfortunately the ES6 Class -syntax does not seem to allow creating function lexically binded to this. I personally think it might as well do that. EDIT: There seems to be experimental ES7 feature to allow it.
I like to use unnamed functions, just implemented a navigation Class which handles this correctly:
this.navToggle.addEventListener('click', () => this.toggleNav() );
then this.toggleNav() can be just a function in the Class.
I know I used to call a named function but it can be any code you put in between like this :
this.navToggle.addEventListener('click', () => { [any code] } );
Because of the arrow you pass the this instance and can use it there.
Pawel had a little different convention but I think its better to use functions because the naming conventions for Classes and Methods in it is the way to go :-)

Categories

Resources