Run function only once when button is clicked? Javascript - javascript

I want the function home(); to execute only once. When the player chooses their weapon it creates a homepage button every time they click one of three buttons.
how can it run once so only one homepage button is created regardless of how many times the game is played again?
https://codepen.io/hamisakim/full/XWjVEbx
function home(){ const button = document.createElement("button");
button.innerHTML = "Homepage";
button.setAttribute("id",'homebutton')
const postDiv = document.querySelector('#choices');
postDiv.appendChild(button);
function buttonClick(e) {
home();
const choices = ["lapis", "papyrus", "scalpellus"];
const randomIndex = Math.floor(Math.random() * choices.length);
computer.currentChoice = choices[randomIndex];
document.querySelector("#homepageText").innerHTML = '';
document.querySelector('h3').innerHTML = 'choose below to play again!';
document.getElementById('choices').removeEventListener('click',null);

Add a check if it has run. A simple boolean
var hasHomeRun = false;
function home(){
if (hasHomeRun) return;
hasHomeRun = true;
...
Other option would be to check to see if the element exists
function home(){
if (document.querySelector('#choices')) return;
...

Since your home() functions adds a <button id='homebutton'>, you could use JS to check if that ID is already exists in the DOM
function home() {
if (!document.getElementById("homebutton")) {
// Create button, since it's not yet there
}
}

i think you need this:
window.runHome = false;
const home = function() {
console.log('run home');
}
const buttonClick = function() {
if(!window.runHome) {
window.runHome = 1;
home();
}
}
<button type="button" onclick="buttonClick()">Click</button>

Related

how to stop function in js?

I have a form that counts when a button (button.clicked in the example below) is clicked. I want to make it operate in two modes: one keeps counting with every click, the other has a timer (started with the click of another button, button.start) that will disable the click-count button when the timer runs out. Each mode is chosen by clicking a button (button.menu-timer and button.menu-clicks). When the count mode is selected, one function (cc) is called. When switched to the timer mode, another function (tt) should be called and the first function should stop.
If I click one mode button, then everything works as it should, but if after that I click the other mode button, both functions continue to operate; each click of button.click adds two to the count. Moreover, if you click the mode buttons several times, clicking the count button will increase the counter many times, rather than only once.
I searched for solutions on the Internet and found one based on return; I tried to use return in various ways but couldn't get it to work.
I need that when choosing the right mode, only the desired function works. And so that when you click several times on one mode, the function will run once.
The following snippet is also available on CodePen.
let clicker = document.querySelector(".click");
let start = document.querySelector(".start");
let clickerValue = document.querySelector(".click").value;
const reset = document.querySelector(".reset");
const menuTimer = document.querySelector(".menu-timer");
const menuClicks = document.querySelector(".menu-clicks");
const times = document.querySelectorAll(".time");
let i = 0;
let y;
let tf;
let timer = 15;
function tt(tf) {
if (tf ===2) {
return;
}
start.addEventListener("click", () => {
start.style.zIndex = "-1";
y = setInterval(() => {
if (i === timer) {
clicker.setAttribute("disabled", "");
} else {
i++;
}
}, 1000);
});
clicker.addEventListener("click", () => {
clicker.textContent = clickerValue++;
});
reset.addEventListener("click", resetF);
}
function cc(tf) {
if (tf = 1) {
return;
}
start.addEventListener("click", () => {
console.log("111111");
start.style.zIndex = "-1";
});
clicker.addEventListener("click", () => {
clicker.textContent = `a ${clickerValue++}`;
});
reset.addEventListener("click", resetF);
}
function resetF() {
clearInterval(y);
i = 0;
start.style.zIndex = "2";
clickerValue = 0;
clicker.textContent = clickerValue;
clicker.removeAttribute("disabled", "");
}
menuTimer.addEventListener("click", function () {
menuTimer.classList.add("active");
menuClicks.classList.remove("active");
tt(1);
resetF();
});
menuClicks.addEventListener("click", function () {
menuClicks.classList.add("active");
menuTimer.classList.remove("active");
cc(2)
resetF();
});
<div class="menu">
<button type="button" onclick="tf = 1" class="menu-timer">TIMER</button>
<button type="button" onclick="tf = 2" class="menu-clicks">CLICKS</button>
</div>
<div class="click-btn">
<button class="click" type="button">0</button>
<button class="start" type="button">START</button>
</div>
<button class="reset" type="button">Reset</button>
You have a typo with assigning = instead of equality operator ===
function cc(tf) {
if (tf = 1) { // should be ===
return;
}
...
}
Also before you addEventListener('click', ...), a good practice is to remove previous click listeners with removeEventListener('click')

push is not a function javascript

I am new in JavaScript. I wrote function to save data to local storage, but every time I try to save a data,it doesnt work and it always get this error: push is not a function.and this error just appears a second in console.log and dissappear by itself. apart from this my problem is it doesn't save the data to the local storage How can I fix this? (the function,not working is "addTodoToStorage"
const form =document.querySelector("#todo-form");
const todoInput = document.querySelector("#todo");
const todolist= document.querySelector(".list-group");
const firstCardBody= document.querySelectorAll(".card-body")[0];
const secondCardBody= document.querySelectorAll(".card-body")[1];
const filter = document.querySelector("#filter");
const clearButton = document.querySelector("clear-todos");
eventListeners();
function eventListeners() {
form.addEventListener("submit",addTodo);
}
function addTodo(e) {
const newTodo = todoInput.value.trim();
if (newTodo === ""){
showAlert("danger","please type something");
}
else{
addTodoToUI(newTodo);
addTodoToStorage(newTodo);
showAlert("success","successfully added");
}
e.preventDefault();
}
function getTodosFromStorage() {
let todos;
if(localStorage.getItem("todos") === null){
todos = [];
}
else{
todos= JSON.parse(localStorage.getItem("todos"));
}
return todos;
}
function addTodoToStorage(newTodo) {
let todos = getTodosFromStorage();
todos.push(newTodo);
localStorage.setItem("todos",JSON.stringify(todos));
}
function showAlert(type,message) {
const alert = document.createElement("div");
alert.className= `alert alert-${type}`;
alert.textContent= message;
firstCardBody.appendChild(alert);
//set time out
setTimeout(function () {
alert.remove();
},2000);
}
function addTodoToUI(newTodo) {
const listItem = document.createElement("li");
const link = document.createElement("a");
link.href="#";
link.className="delete-item";
link.innerHTML='<i class = "fa fa-remove"></i>';
listItem.className="list-group-item d-flex justify-content-between";
listItem.appendChild(document.createTextNode(newTodo));
listItem.appendChild(link);
todolist.appendChild(listItem);
todoInput.value = "";
}
As for the error msg disappearing after a while, it's because you haven't prevented the default behavior of the form. Or rather, you have prevented it at the bottom, which is fine if you want the form to prevent GET-ing(its default method) to the URL(in action attribute).
But if an error occurs inside the submit handler (in your case, it probably does), the JavaScript stops the execution, and the form continues its default behavior, because there's is nothing to tell it that it can't do that. It can, so it will. HTML forms be ratchet like that. Due to which the page reloads.
For the part why there's an error, I couldn't find any errors here. If this is the same code you're using, there shouldn't be a problem.
EDIT
I tried to reproduce your example as follows and it works. It adds items to my localStorage successfully.
<form id="todo-form">
<input id="todo" type="text" />
</form>
<script>
const form =document.querySelector("#todo-form");
const todoInput = document.querySelector("#todo");
eventListeners();
function eventListeners() {
form.addEventListener("submit",addTodo);
}
function addTodo(e) {
e.preventDefault();
const newTodo = todoInput.value.trim();
if (newTodo === ""){
showAlert("danger","please type something");
}
else{
addTodoToStorage(newTodo);
showAlert("success","successfully added");
}
}
function getTodosFromStorage() {
let todos;
if(localStorage.getItem("todos") === null){
todos = [];
}
else{
todos= JSON.parse(localStorage.getItem("todos"));
}
return todos;
}
function addTodoToStorage(newTodo) {
let todos = getTodosFromStorage();
todos.push(newTodo);
localStorage.setItem("todos",JSON.stringify(todos));
}
function showAlert(type,message) {
const alert = document.createElement("div");
alert.className = `alert alert-${type}`;
alert.textContent = message;
document.body.appendChild(alert);
//set time out
setTimeout(function () {
alert.remove();
}, 2000);
}
</script>

Avoid numbers incrementing multiple times when calling a function multiple times

I have written a quantity selector function to display on a page. The page can open some modals, which need to have another quantity selector within each.
I am calling the function within the main page, and also within the modal (to enable the functionality once the modal is displayed.)
When I adjust the quantity in the modal, close the modal, and adjust the quantity on the main page, the quantity increments/decrements double (or 3 times if I was to call the function 3 times.)
Is there a way to "reset" each of these event listeners/functions, to only adjust for their respective elements?
I've looked into "removeEventListener" but haven't had any joy in implementing this within my code.
Example of my work so far here (you can see what I mean if you click the buttons.)
https://codepen.io/777333/pen/zYoKYRN
const quantitySelector = () => {
const qtyGroups = document.querySelectorAll('.qty-group');
if(qtyGroups) {
qtyGroups.forEach((qtyGroup) => {
const qtyDecrease = qtyGroup.querySelector('[data-quantity-decrease]');
const qtyIncrease = qtyGroup.querySelector('[data-quantity-increase]');
const qtyInput = qtyGroup.querySelector('[data-quantity-input]');
const disableEnableDecrease = () => {
if(qtyInput.value == 1) {
qtyDecrease.disabled = true;
} else {
qtyDecrease.disabled = false;
}
};
qtyDecrease.addEventListener('click', (event) => {
event.preventDefault();
if(qtyInput.value > 1) {
qtyInput.value--;
}
disableEnableDecrease();
});
qtyIncrease.addEventListener('click', (event) => {
event.preventDefault();
qtyInput.value++;
disableEnableDecrease();
});
qtyInput.addEventListener('keyup', () => {
disableEnableDecrease();
});
});
}
};
quantitySelector(); // called within main page
quantitySelector(); // called within modal
The issue at hand is that each time you're calling the function, a new event handler is added on top of the previous ones. The best way to avoid this is through Event Delegation where you add a global event handler only once.
// A global event handler
document.addEventListener(
"click",
function (event) {
// Find the qty-group if clicked on it
const qtyGroup = event.target.closest(".qty-group");
// Stop if the click was elsewhere
if (qtyGroup) {
// Get your elements
const qtyDecrease = qtyGroup.querySelector("[data-quantity-decrease]");
const qtyIncrease = qtyGroup.querySelector("[data-quantity-increase]");
const qtyInput = qtyGroup.querySelector("[data-quantity-input]");
const disableEnableDecrease = () => {
if (qtyInput.value == 1) {
qtyDecrease.disabled = true;
} else {
qtyDecrease.disabled = false;
}
};
// Match your elements against what was clicked on.
if (event.target == qtyDecrease) {
event.preventDefault();
if (qtyInput.value > 1) {
qtyInput.value--;
}
disableEnableDecrease();
}
if (event.target == qtyIncrease) {
event.preventDefault();
qtyInput.value++;
disableEnableDecrease();
}
}
},
false
);
Instead of listening to individual elements, you can capture all the clicks on the document, and then finding those that click on elements of interest. You can make a second event handler for the keyup event.
You can save the value of qtyInput on mousedown event and then in the increment you add or subtract one from the saved value instead of the current value of the input.
const quantitySelector = () => {
const qtyGroups = document.querySelectorAll('.qty-group');
if(qtyGroups) {
qtyGroups.forEach((qtyGroup) => {
const qtyDecrease = qtyGroup.querySelector('[data-quantity-decrease]');
const qtyIncrease = qtyGroup.querySelector('[data-quantity-increase]');
const qtyInput = qtyGroup.querySelector('[data-quantity-input]');
const disableEnableDecrease = () => {
if(qtyInput.value == 1) {
qtyDecrease.disabled = true;
} else {
qtyDecrease.disabled = false;
}
};
let savedValue = null;
const saveState = (evebt) => savedValue = Number(qtyInput.value);
qtyDecrease.addEventListener('mousedown', saveState)
qtyIncrease.addEventListener('mousedown', saveState)
qtyDecrease.addEventListener('click', (event) => {
event.preventDefault();
event.stopPropagation();
if(qtyInput.value > 1) {
qtyInput.value = savedValue - 1;
}
disableEnableDecrease();
});
qtyIncrease.addEventListener('click', (event) => {
event.preventDefault();
event.stopPropagation();
qtyInput.value = savedValue + 1;
disableEnableDecrease();
});
qtyInput.addEventListener('keyup', () => {
disableEnableDecrease();
event.stopPropagation();
});
});
}
};
quantitySelector();
quantitySelector();
There is a method called removeEventListener (MDN) but I suggest you to reshape your code such that you do not add event listener if they are already present.
Put all of your addEventListener just when you create your elements, or in a "document ready" callback if they are instantiated by HTML code. Then, when you open your modal, just update your values.
UPDATING YOUR CODE
// hide/show modal function
function toggleModal() {
let modal = document.getElementById('modal');
modal.style.display = modal.style.display == 'none' ? 'block' : 'none';
}
// your document ready function
function onReady() {
const qtyGroups = document.querySelectorAll('.qty-group');
if(qtyGroups) {
qtyGroups.forEach((qtyGroup) => {
const qtyDecrease = qtyGroup.querySelector('[data-quantity-decrease]');
const qtyIncrease = qtyGroup.querySelector('[data-quantity-increase]');
const qtyInput = qtyGroup.querySelector('[data-quantity-input]');
const disableEnableDecrease = () => {
if(qtyInput.value == 1) {
qtyDecrease.disabled = true;
} else {
qtyDecrease.disabled = false;
}
};
qtyDecrease.addEventListener('click', (event) => {
event.preventDefault();
if(qtyInput.value > 1) {
qtyInput.value--;
}
disableEnableDecrease();
});
qtyIncrease.addEventListener('click', (event) => {
event.preventDefault();
qtyInput.value++;
disableEnableDecrease();
});
qtyInput.addEventListener('keyup', () => {
disableEnableDecrease();
});
});
}
// attach hide/show modal handler
const toggle = document.getElementById('modal_toggle');
toggle.addEventListener('click', toggleModal);
}
onReady();
<div class="qty-group">
<button data-quantity-decrease disabled>-</button>
<input data-quantity-input value="1">
<button data-quantity-increase>+</button>
</div>
<div class="qty-group" id="modal" style="display: none;">
<button data-quantity-decrease disabled>-</button>
<input data-quantity-input value="1">
<button data-quantity-increase>+</button>
</div>
<button id="modal_toggle">Toggle Modal</button>
REFACTORING
It is better in such cases to reason as Components. Components ensure code encapsulation, maintainability, reusage, single responsability and many other usefull principles:
// hide/show modal function
function toggleModal() {
// get the modal
let modal = document.getElementById('modal');
// hide the modal
modal.style.display = modal.style.display == 'none' ? 'block' : 'none';
// reset the input of the modal
modalInputReference.reset();
}
function createQuantityInput(target, initialQuantity=1, min=1, max=10, step=1) {
let quantity = 0;
// assign and check if should be disable, also bind to input value
let assign = (q) => {
quantity = Math.max(Math.min(q, max), min);
decrease.disabled = quantity <= min;
increase.disabled = quantity >= max;
input.value = quantity;
};
// CREATION
// This part is not mandatory, you can also get the elements from
// the target (document.querySelector('button.decrease') or similar)
// and then attach the listener.
// Creation is better: ensure encapsulation and single responsability
// create decrease button
let decrease = document.createElement('button');
decrease.addEventListener('click', () => { assign(quantity - step); });
decrease.innerText = '-';
// create increase button
let increase = document.createElement('button');
increase.addEventListener('click', () => { assign(quantity + step); });
increase.innerText = '+'
// create input field
let input = document.createElement('input');
input.value = quantity
input.addEventListener('change', () => { assign(parseFloat(input.value)); });
// resetting the quantity
assign(initialQuantity);
// appending the new component to its parent
target.appendChild(decrease);
target.appendChild(input);
target.appendChild(increase);
// return a reference to manipulate this component
return {
get quantity() { return quantity; },
set quantity(q) { assign(q); },
assign,
reset: () => assign(initialQuantity)
};
}
// this will be your modal reference
let modalInputReference;
function onReady() {
// inject all qty-group with a "quantityInput" component
document.querySelectorAll('.qty-group').forEach(elem => {
let input = createQuantityInput(elem);
if (elem.id == 'modal') {
// if it is the modal I save it for later use
// this is just an hack for now,
// a full code should split this part into a "modal" component maybe
modalInputReference = input;
}
});
// emualte the modal
let toggle = document.getElementById('modal_toggle')
toggle.addEventListener('click', toggleModal)
}
// this function should be wrapped by a
// $(document).ready(onReady) or any other
// function that ensure that all the DOM is successfully loaded
// and the code is not executed before the browser has generated
// all the elements present in the HTML
onReady();
<div class="qty-group"></div>
<div class="qty-group" id="modal" style="display: none;"></div>
<button id="modal_toggle">Toggle Modal</button>
It is shorter (without comments) and also more maintenable. Don't trust who says it is overengineered, it is just kind of time to learn to reason this way, then is much easier and faster. It is just a time investment to waste less time in the future. Try figure out why React or Angular(JS) have climbed the charts of the best frameworks so fast.

Toggling not working properly - Javascript

I have this code which sould toggle between elements, if user opens the element all other elements should close and that works, but if I click on the same element multiple times nothing happens and I cant figure out why. I need some help.
Here is my code:
const heads = [...document.querySelectorAll(".head")];
const dropdowns = [...document.querySelectorAll(".head-dropdown")];
const activate_filter = document.getElementById("filter-btn");
let isActive = false;
let isFilterActive = false;
let queue = [];
if (!isFilterActive) {
dropdowns.forEach((val, i) => {
val
.querySelector(".dropdown-search")
.setAttribute(
"name",
`${heads[i].querySelector("p").textContent}-search`
);
});
}
activate_filter.addEventListener("click", e => {
dropdowns.forEach((val, i) => {
val.classList.remove("activated");
val.dataset.isActivated = false;
});
if (!isFilterActive) {
heads.forEach((val, i) => {
const p = val.children[0];
p.innerHTML += `<i class='eos-icons'>arrow_drop_down</i>`;
val.addEventListener("click", handleEventAll);
val.style.cursor = "pointer";
});
isFilterActive = true;
} else {
heads.forEach(val => {
const p = val.children[0];
const i = p.querySelector("i");
if (i) {
i.remove();
}
val.removeEventListener("click", handleEventAll);
val.style.cursor = "default";
});
isFilterActive = false;
}
});
function handleEventAll(e) {
const test = e.target.querySelector(".head-dropdown");
test.classList.add("activated");
queue.unshift(test);
if (queue.length > 3) {
queue.pop();
}
dropdowns.forEach((val, i) => {
if (!val.dataset) {
val.dataset.isActivated = false;
}
let t = eval(queue[0].dataset.isActivated);
console.log(t);
if (t) {
console.log("in true", queue[0]);
queue[0].dataset.isActivated = true;
queue[0].classList.toggle("activated");
queue.pop();
} else {
console.log("in false", queue[0].dataset);
queue[0].dataset.isActivated = false;
console.log(queue);
queue[1].classList.remove("activated");
queue.pop();
}
});
}
window.addEventListener("click", e => {});
The elements are added to the queue and the queue sorts it out if previous element which was opened is properly closed and it opens a new element which is clicked. If the same element is clicked twice the queue closes the element but does not open it again until some other element is clicked.
Here is codepen for full experience: https://codepen.io/darioKolic/pen/eYdYzdy
I rewrote the handleEventAll function to make the required functionality without using a queue. check this
What I did is save the current state (is activated class exist or not) for clicked element, and then I remove all activated class from all element then add or remove the class activated depends on the old value.

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()} )

Categories

Resources