Facing problem in JavaScript function execution - javascript

I am trying to make a simple Shopping List App in which user can Add, Delete and mark the task done when completed. So far, I am able to add the task but facing problem in executing the done and delete functions. I am getting an error because when I execute it, the done and delete buttons are not there but what should I do to fix it?
var inp = document.getElementById("form");
var button = document.getElementById("click");
//Create List Function with Done and Delete Buttons
function addVal() {
var ul = document.getElementById("list");
var li = document.createElement("li");
var span = document.createElement("span");
var done = document.createElement("button");
var del = document.createElement("button");
li.appendChild(document.createTextNode(""));
span.appendChild(document.createTextNode(inp.value));
done.appendChild(document.createTextNode("Done"));
del.appendChild(document.createTextNode("Delete"));
li.appendChild(span);
li.appendChild(done);
li.appendChild(del);
done.setAttribute("class", "doneBut");
del.setAttribute("class", "delBut");
ul.appendChild(li);
inp.value = "";
}
//Get Input Length
function checkLength() {
return inp.value.length;
}
//Run function on Button Click
function onButtonClick() {
if (checkLength() > 0) {
addVal();
}
}
//Run function on Enter Keypress
function onEnter(event) {
if (checkLength() > 0 && event.which === 13) {
addVal();
}
}
//Trigger Events
button.addEventListener("click", onButtonClick);
inp.addEventListener("keypress", onEnter);
//Done and Delete Button Functions
var doneButton = document.getElementsByClassName("doneBut");
var deleteButton = document.getElementsByClassName("delBut");
function doneTask() {
doneButton.parentNode.classList.add("done");
}
function delTask() {
deleteButton.parentNode.classList.add("delete");
}
doneButton.addEventListener("click", doneTask);
deleteButton.addEventListener("click", delTask);
<input type="text" placeholder="Enter Your Task..." id="form" />
<button id="click">Add Task</button>
<h2>List:</h2>
<ul id="list"></ul>
Please Help.

Your problem is that the code tries to add events before the buttons exist. The buttons don’t exist until the addVal function gets called. Since addVal is not being called before the you try to add your event handlers, the getElementById returns null, and you attempt to add an event listener to null.
Additionally it looks like you’re planning to add multiple done and delete buttons. That wouldn’t normally be a problem, except you’re referencing them by ID, and IDs MUST be unique. You’ll need to switch this to a class or an attribute, since you’ll need one per item in the shopping cart.
You’ll probably want to look into event delegation, so that you can add your events once to the page before any buttons exist. https://javascript.info/event-delegation

It's most likely because your script is running before your code is running. Add the <script> tags just before the closing </body> tag to fix it:
<script>/* Your code here */</script>
</body>

You need to place this in a window.onload function, or run it in a function inside of the body tag's onload. Those elements don't exist yet when the script is run:
window.onload = function() {
var inp = document.getElementById("form");
var button = document.getElementById("click");
button.addEventListener("click", onButtonClick);
inp.addEventListener("keypress", onEnter);
}

Related

Event firing multiple times

I have a little problem that I replicated in the little code snippet below (in the most simple way possible, however it still shows the problem I am facing).
Here is the snippet :
const searchBar = document.getElementById('search');
const resBox = document.getElementById('results');
searchBar.addEventListener('input', function handler(e) {
if (e.target.value === '') {
resBox.innerHTML = '';
return false;
}
setTimeout(() => populate(e), 300);
});
function populate(e) {
const btnBox = document.createElement('div');
for (let i = 1; i <= 3; i++) {
const btn = document.createElement('button');
btn.classList.add('js-click')
btn.innerText = 'Click me';
btnBox.appendChild(btn);
}
resBox.appendChild(btnBox);
dynamicBtnClickListener();
}
function dynamicBtnClickListener() {
resBox.addEventListener('click', function handler(e) {
console.log('You clicked me !');
});
// THE SOLUTION I FOUND FOR THE MOMENT :
//const btns = document.querySelectorAll('button.js-click');
//btns.forEach(btn => {
// btn.addEventListener('click', function handler(e) {
// console.log('You clicked me !');
// });
//});
}
<input type="text" id="search">
<div id="results"></div>
As you can see in the snippet, I have a first listener on input that generates a list of buttons when you type in it. When it is empty, the buttons disappear. In my real world case, it is a search input, that when a user types in, calls a function that populates a result box below it with results from DB.
I then have an on click listener on the buttons. In the code snippet, I simply put a console('You clicked me') when you click on the buttons. In my real app, it takes the result item (each result is an user) and inserts it in a table.
The problem appears when you open, close, then re-open the results box. This is done by inputing something, clearing the input, then re-input something. When you do that and then click on one of the buttons, it fires the click event on them as many times as you opened / closed the result box, so you will see the "You clicked me" on console multiple times.
I have done some research and most of it calls for using event.stopPropagation() and / or removing the event listener(s). I did try all these possible solutions, in every way I could think of, but I couldn't make it work.
Anyways I found a way around this (the commented portion of the dynamicBtnClickListener() function), but I feel it is not optimal. It consists of getting all the buttons with querySelectorAll(), then loop through them and add the click listener to every one of them, but I do not think it is optimal nor best-practice like. This is why I come here to ask if maybe there is a better solution, possibly one that keeps the click listener on the results box (if that is the most optimal solution. Is it by the way ?).
So even though I found a solution to this problem, could someone please tell me what is the best practice and optimal way of doing this ?
Thank you very much for your help
Each time the you type in the text area, resBox is accessed each time and the actual element resBox gets a new event listener every time(the buttons don't have any specific listener themselves, so I make EACH BUTTON have a specific listener individually)
const searchBar = document.getElementById('search');
const resBox = document.getElementById('results');
searchBar.addEventListener('input', function handler(e) {
if (e.target.value === '') {
resBox.innerHTML = '';
return false;
}
setTimeout(() => populate(e), 300);
});
function populate(e) {
const btnBox = document.createElement('div');
for (let i = 1; i <= 3; i++) {
const btn = document.createElement('button');
btn.classList.add('js-click')
btn.innerText = 'Click me';
btn.addEventListener('click',function(ev){console.log('You clicked me !')})
btnBox.appendChild(btn);
}
resBox.appendChild(btnBox);
}
<input type="text" id="search">
<div id="results"></div>
Now, here is an example that only has one event listener but would completely handle the situation >:D
Technically this should be faster(since one event listener compared to many), but personally I prefer this option because it "feels better" due to one function controlling the whole button layout(which would make it less "rigid")
PS: The speed difference is so insignificant, you can pick and choose(but if a whole chuck ton of buttons, yea this becomes better)
const searchBar = document.getElementById('search');
const resBox = document.getElementById('results');
const randChars=()=>{ //random characters to prove ONE event listener can work for multiple buttons in resBox
let arr=["a","b","c","d","e"]
let randIndex=()=>Math.floor(Math.random()*arr.length)||1
let n=randIndex(); let returnChar=""
for(let i=0;i<n;i++){returnChar+=arr[randIndex()]}
return returnChar
}
searchBar.addEventListener('input', function handler(e) {
if (e.target.value === '') {
resBox.innerHTML = '';
return false;
}
setTimeout(() => populate(e), 300);
});
resBox.addEventListener('click',function(ev){ //single event listener for all buttons
let currentButton=ev.path[0]
if(currentButton.tagName!="BUTTON"){return;} //if a button was not clicked
console.log("Button with text\n'"+currentButton.innerText+"'\nwas clicked")
})
function populate(e) {
const btnBox = document.createElement('div');
for (let i = 1; i <= 3; i++) {
const btn = document.createElement('button');
btn.classList.add('js-click')
btn.innerText = 'Click me '+randChars();
btnBox.appendChild(btn);
}
resBox.appendChild(btnBox);
}
<input type="text" id="search">
<div id="results"></div>

Toggle text in button onclick

I'm trying to toggle the text in a button every time it's clicked, between "READ" and "NOT READ". The buttons have been created dynamically with js and placed into an HTML table. Each button has a unique ID, but the same class name.
I've written an if statement that works for the first button that is set in the table, but the same if statement wont work for the buttons created dynamically.
I've tried lots of different variations for the if statements. I'm not sure if the best way would be to access the unique id's, but I don't know how to do that.
Any help is appreciated! Thanks
Here's a repl https://repl.it/repls/SpryVisibleMining
function toggleText(){
if (readButton.innerHTML == "READ"){
readButton.innerHTML = "NOT READ";
} else if (readButton.innerHTML == "NOT READ"){
readButton.innerHTML = "READ";
} else {
null
}
}
And this is if statement that wont wont do anything
function toggleOthers() {
let toggle = document.getElementsByClassName(".readBtn")
toggle[0].addEventListener("click", () => {
if (toggle.innerHTML == "READ") {
toggle.innerHTML = "NOT READ"
} else if (toggle.innerHTML == "NOT READ") {
toggle.innerHTML = "READ"
} else {
null
}
})
}
toggleOthers()
The problem lies in how you are listening for the click events. Your toggleText function is triggered whenever you click the #readed button with the onclick attribute. But inside the toggleText function you add another event listener to the same button, adding a new event listener every time you click the button.
So every time you click the button you increment the amount of times you are calling toggleText.
Remove the onclick from the button and change the id to a class attribute. You said you would have multiple buttons, so having multiple buttons with the same id won't do it.
<button class="readed">READ</button>
Because you want to listen for the click event on dynamically created elements I suggest you use Event Delegation. This means listening for the click event on a parent element, this could be your table#shelf element, and check which element has been clicked. If A has been clicked, then do X, if B has been clicked, then do Y.
Listen for click event on your table element.
var table = document.getElementById('shelf');
table.addEventListener("click", tableClickHandler);
In tableClickHandler check which element has been clicked. You can do it by getting the clicked target and use the closest method to see if it really is the element you want to be clicked.
For example when you would have a <span> in your <button>, event.target would be <span>. But you want the <button> element. closest goes up in the DOM tree to see if it finally reaches an element that is the <button> you want and returns it.
You can do this for any button inside of your table.
function tableClickHandler(event) {
var readed = event.target.closest('.readed');
if (readed) {
toggleText(readed);
}
}
Modify your toggleText function so that it can take any <button> you throw add it that you want the text toggled in. Add a button parameter which represents the current button.
// Toggle text when clicked.
function toggleText(button) {
if (button.innerHTML == "READ") {
button.innerHTML = "NOT READ";
} else if (button.innerHTML == "NOT READ") {
button.innerHTML = "READ";
} else {
null
}
}
For example you can use this:
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<button onclick="toggle(this)">not read</button>
<script>
function toggle(e) {
let txt = e.innerText;
e.innerText = txt == 'not read' ? 'read' : 'not read';
}
</script>
</body>
</html>
Let me know if it's not suitable for your use case ...
And also you can use querySelectorAll() to get all buttons and then set this event with a for() loop.
Try
toggleText = b=> b.innerHTML = b.innerHTML=='READ'?'NOT READ':'READ'
<button id="A" onclick="toggleText(this)">NOT READ</button>
<button id="B" onclick="toggleText(this)">NOT READ</button>
According to your repl, you also registered an onclick-Handler on your button, too.
So calling toggleText() while clicking the "Read/Not-Read"-button, will effectively register another onclick-Handler. This will repeat as often as you press the button.
This will run as follows:
run onclick-Handler toggleText(); this will register another EventListener
run the EventListener (registered first by index.js)
run the new registered EventListener (registered by button.onclick)
rinse and repeat ...
Just remove it in your HTML:
<td><button id="readed">READ</button></td>
You have to init an array named books, and each time you add a new book, you have to push the new book to the books array.
And also you have to set a flag as hasReadBook to the Book class.
When you are going to render your table row you have to write an if, for making the flag to string in dom.
function updateTable() {
//anythings needs to done for updating table.
//for hasReadBook flag you should do like this:
const hasReadBookString = books[i].hasReadBook ? "Read" : "Not Read";
}
And you need to make a loop on readBtn HTML collection, to know which index is going to change:
let books = [{...}];
let toggles = document.getElementsByClassName(".readBtn");
for (var i = 0; i < toggles.length; i++){
labels[i].addEventListener('click', function(e) {
books[i].hasReadBook = !books[i].hasReadBook;
})
}
updateTable();

Event listeners/onclick events for buttons created in javascript function

I have a javascript button (button A), when it is clicked, it generates some new elements, including another button (button B). I would like a way to listen on button B, and then execute a separate function.
I tried editing Button B's 'onclick' attribute in javascript. This did not work.
HTML:
<input id="addTaskButton" type="submit" value="add task" onclick="addTaskFunction()"></input>
Javascript:
function buttonB()
{
// Not working
}
function addTaskFunction()
{
var doneButton = document.createElement("BUTTON");
doneButton.id = "doneButton";
doneButton.innerHTML = "DONE";
doneButton.onclick = "buttonB()";
}
i am expecting the listener to perform buttonB when ran. Instead, i get no response.
Correct use is as follows
function addTaskFunction()
{
var doneButton = document.createElement("BUTTON");
doneButton.id = "doneButton";
doneButton.innerHTML = "DONE";
doneButton.onclick = buttonB;
}
This works for me:
doneButton.onclick = function(){buttonB()};

How to make button do more than one thing after use

First I displayed the div on screen and now I want to use the the button to create that same div(or any other action) after user input.
document.getElementById("add").addEventListener("click", function() {
document.getElementById("welcome").style.display = "block";
}
How do i make the button be able to work again after the first thing it did?
It isn't customary to reuse a UI element in different ways as it tends to confuse the end user. But if you must...
document.getElementById("add").addEventListener("click", showWelcome);
function showWelcome() {
document.getElementById("welcome").style.display = "block";
document.getElementById("add").removeEventListener("click", showWelcome);
reassignButton();
}
function resassignButton(){
// some decision logic for next button function
document.getElementById("add").addEventListener("click", doNextThing);
}
function doNextThing(){
// removes eventlistener on add button. does whatever
}
You can use removeEventListener to remove the handler and then set a new one.
var ele = document.getElementById('btn');
ele.addEventListener("click", function click1(){
//Do stuff for the first click
this.innerHTML = "Click Me Again";
alert("Hello");
//Remove the event hendler
this.removeEventListener("click", click1, true);
//Attach handler for rest of clicks
ele.addEventListener("click", function click2(){
alert("You cicked again!");
}, true);
}, true);
<button id=btn>Click Me</button>
Although it's probably more practical to re-use a single event listener, as assigning and re-assigning event listeners can sometimes lead to memory leaks..
(()=>{
var ele = document.getElementById('btn');
var clicks = 0;
ele.addEventListener("click", function(){
if(clicks){
alert("Thanks for clicking again");
}else{
alert("Hello");
this.innerHTML = "Click again";
}
clicks++;
}, true);
})();
<button id=btn>Click Me</button>
You can make several methods and use these with the onclick attribute of button.
Also I advise you to use jQuery. It's easy and faster than JS.

How to tell if a button is clicked

I have this code:
<script type="text/javascript">
function changestate()
{
var StateTextBox = document.getElementById("State");
var IgnoreTextBox = document.getElementById("Ignore");
var PlayButton = document.getElementById("Play");
if(document.getElementById("Play").onclick == true)
{
StateTextBox.value = "Happy";
}
}
</script>
<input TYPE="button" VALUE="Play with Pogo" id="Play" onClick="changestate();"/>
I'm trying to know when the button is clicked, and have that if button is clicked in the if statement. I want to know this so I can change the value that is inside the text box. The problem is, I do not know how to tell when the button is clicked. If you could help me out that would be great.
The onclick attribute identifies what should happen when the user clicks this particular element. In your case, you're asking that a function be ran; when the function runs, you can rest assured that the button was clicked - that is after all how the function itself got put into motion (unless you invoked it some other way).
Your code is a bit confusing, but suppose you had two buttons and you wanted to know which one was clicked, informing the user via the stateTextBox value:
(function () {
// Enables stricter rules for JavaScript
"use strict";
// Reference two buttons, and a textbox
var playButton = document.getElementById("play"),
stateTextBox = document.getElementById("state"),
ignoreButton = document.getElementById("ignore");
// Function that changes the value of our stateTextBox
function changeState(event) {
stateTextBox.value = event.target.id + " was clicked";
}
// Event handlers for when we click on a button
playButton.addEventListener("click", changeState, false);
ignoreButton.addEventListener("click", changeState, false);
}());
You can test this code live at http://jsfiddle.net/Y53LA/.
Note how we add event-listeners on our playButton and ignoreButton. This permits us to keep our HTML clean (no need for an onclick attribute). Both of these will fire off the changeState function anytime the user clicks on them.
Within the changeState function we have access to an event object. This gives us some details about the particular event that took place (in this case, the click event). Part of this object is the target, which is the element that was clicked. We can grab the id property from that element, and place it into the value of the stateTextBox.
Here is the adjusted HTML:
<input type="button" value="Play with Pogo" id="play" />
<input type="text" id="state" />
<input type="button" value="Ignore with Pogo" id="ignore" />​
You can know if button clicked by using a flag (true or false).
var flag = false;
window.addEventListener("load", changestate, false);
function changestate() {
var StateTextBox = document.getElementById("State");
var PlayButton = document.getElementById("Play");
PlayButton.addEventListener("click", function () {
flag = true;
})
PlayButton.addEventListener("click", change)
function change() {
if (flag) {
StateTextBox.value = "Happy";
}
}
}
Looking back on this, many years later, you could simply do:
<script type="text/javascript">
function changestate(action)
{
var StateTextBox = document.getElementById("State");
var IgnoreTextBox = document.getElementById("Ignore");
var PlayButton = document.getElementById("Play");
if(action == "Play")
{
StateTextBox.value = "Happy";
}
}
</script>
<input TYPE="button" VALUE="Play with Pogo" id="Play" onClick='changestate("Play");'/>

Categories