I am trying to use the on-click event in my javascript file. But, whenever, I use it, it says that the function is declared, but never used. Can you please help me resolve this issue? You can check the code below, as well as in the image.
let showtoDos = () => {
let li = "";
if(toDos) {
toDos.forEach((todo, id) => {
li += `<li class="task">
<label for="${id}">
<input class="clicked" type="checkbox" id="${id}" />
<p>${todo.name}</p>
</label>
<div class="settings">
<i id="ellipsis" class="fa-solid fa-ellipsis"></i>
<ul class="task-menu">
<li><i class="fa-solid fa-pen"></i>Edit</li>
<li onclick="deleteTask(${id})" class="delete" ><i class="fa-solid fa-trash"></i>Delete</li>
</ul>
</div>
</li>`;
});
}
taskBox.innerHTML = li;
}
showtoDos();
function deleteTask(deletedId) {
console.log(deletedId);
}
Moreover, you can check in the below image that, whenever I click on the delete button on the browser, it gives a reference error.
You have two different problems there:
The IDE is doing validation of your code showing warnings and can't
find any line calling that function. The only point you targeted
that function is when you defined the onclick attribute of the li
element but it happened inside a string literal and it doesn't count
for the linter. You can ignore this.
The javascript code you have in that file won't run in the global
scope so the function defined there won't be visible to the li item
when its event will fire. You should try attaching an event handler
via javascript so to be sure the handler will be attached until you
still have control of that scope.
Just after you showToDos() you can try to run the following:
//for each delete list item
document.querySelectorAll('li.task ul.task_menu li.delete')
.forEach( (o, i) => {
//bind an handler (deleteTask) for its click event (deleteTask should be in this scope now)
o.addEventListener("click", deleteTask);
});
DISCLAIMER:
Unfortunately that selector I used with querySelectorAll will get ALL those items even those that already got the handler attached (if any already existed with the same selector criteria). Before doing addEventListener, one strategy could be to check if there's already an handler for that event on that object.
A better solution would be to use that html code you crafted with a template string, create an html elements tree from there and call querySelectorAll(+attachEventHandler) from such object before appending it to taskbox. It wouldn't require you to make any check before attaching the event handler because you'll be targeting only the newly created ones.
Or you could as someone suggested to make that function available in the global scope but it wouldn't scale really well due to name collisions (this is one of the unsafe ways to do it):
var deleteTask = (/*args*/) => {/*code here*/}
You need to place your function in the global scope (window object in browser) if you want to access it like that, try:
window.deleteTask = function (deletedId) {
console.log(deletedId);
}
Related
I began using svelte for a recent project, and although I like the workflow of the framework so far, I've yet to get a single function to work successfully.
Currently, I'm trying to change the innerHTML of a series of objects using functions.
Below is my code:
<head>
<script>
export let question1() {
document.getElementByClass(questionBox).innerHTML = "True or False?";
document.getElementById(ans_1).innerHTML = "True";
document.getElementById(ans_2).innerHTML = "False";}
</script>
</head>
<body>
<div class="container">
<button on:click={question1} class="startButton">Start Game</button>
<div class="box"><span id="questionBox">...</span></div>
</div>
<div class="option-container">
<button class="option" id="ans_1">option1</button>
<button class="option" id="ans_2">option2</button>
</div>
</body>
There is an error marked beneath my function when I call it on:click in the button, and that error reads as follows:
'question1' is not defined. Consider adding a <script> block with 'export let question1' to declare a propsvelte(missing-declaration)
I am quite new to svelte and it's entirely possible I misunderstood something structurally within my code, but I've checked all over and can't seem to find anything that quite addresses my problem.
Any help would be quite appreciated. Perhaps I just need some new eyes on this.
Thank you.
Here's the list of things you might have gotten wrong.
Function declaration
This is valid:
function question1() {
//dosomething
}
This is valid too (arrow function):
let question1 = () => {
//dosomething
}
But this is not a correct way:
let question1() {
//dosomething
}
getElementByClass is not a correct method. You probably meant getElementsByClassName.
document.getElementByClassName("questionBox").innerHTML = "something"
Note that if you have more than one element with that class name, only the first item will be affected.
Easiest way to get a single element is to use:
//by class name
document.querySelector(".classname")
//by id
document.querySelector("#id")
//by element type
document.querySelector("div")
You dont need to add <head> tag in your code. Each svelte file can have a <script> and <style> element in the component at top level.
You are trying to change text in elements in a Vanilla JS way. You should probably populate the DOM using data so that you are taking advantage of Svelte's amazing reactivity. Look at this REPL to see a replication of what you are trying to do in a more Svelty way. Basically, use data to dynamically render the DOM elements. That way, you will never directly manipulate the DOM Elements. Just change your data and Svelte takes care of changing the DOM.
https://svelte.dev/repl/8316ae63d83b443aaef5aa7b29c36dc1?version=3.53.1
Use betternames for your functions. question1 as a function name is not descriptive of what you are doing inside.
If you still want to modify the DOM elements directly, you can bind them to variables like so and change text like so:
https://svelte.dev/tutorial/bind-this
I would like to attach an event listener (that calls a function) to a button that is not yet present in the DOM. I don't want to use inline-call in the HTML how can I do it?
So far i could achieve what I wanted by creating a global event listener that checks if the element clicked has a specific Id. But I found this solution to be dirty and not optimal.
const messageForm = document.querySelector("#message-form")
const messageTextarea = document.querySelector("#message-textarea");
const messageList = document.querySelector("#message-list");
const messageEmpty = document.querySelector("#message-empty");
let messageNumber = 0;
const messageFormatted = messageText => {
return `
<li class="message-item">
<img class="message-avatar" src="./icons/boy.png" alt="Username">
<article class="message-content">
<p class="author">Ernesto Campese</p>
<p class="message">${messageText}</p>
<p class="datetime">A moment ago</p>
</article>
<div class="message-actions">
<img id="message-edit" class="action-button" src="./icons/edit.png" alt="" width="22" height="22">
<img id="message-delete" class="action-button" src="./icons/delete.png" alt="" width="22" height="22">
</div>
</li>
`;
}
document.addEventListener("click", (e) => {
if (e.target.id === "message-delete") {
e.target.parentNode.parentNode.remove();
messageNumber--;
messageEmptyCheck();
}
})
messageForm.addEventListener("submit", (e) => {
e.preventDefault();
if (messageTextarea.value !== "") {
messageList.insertAdjacentHTML("beforeend", messageFormatted(messageTextarea.value));
messageTextarea.value = null;
messageTextarea.focus();
messageNumber++;
messageEmptyCheck();
}
});
As you can see in the code, inside the <li> that I'm creating there are two IMG, one is for deleting and the other for editing. I want to add an event listener to the delete IMG, so when the user clicks it, the li gets eliminated.
The problem is that I cannot create any function if the element does not exist yet. I would love to do something like:
const messageDeleteButton = document.querySelector("#message-delete");
messageDeleteButton.addEventListener("click", (e) => {
e.parentNode.parentNode.remove();
}
})
Hope I was clear enough, thank you guys!
You could parse your HTML string to a document in JS and then add the event listener to that element.
So you've got your Template Literal string.
const messageFormatted = messageText => {
return `
<li class="message-item">
<img class="message-avatar" src="./icons/boy.png" alt="Username">
<article class="message-content">
<p class="author">Ernesto Campese</p>
<p class="message">${messageText}</p>
<p class="datetime">A moment ago</p>
</article>
<div class="message-actions">
<img id="message-edit" class="action-button" src="./icons/edit.png" alt="" width="22" height="22">
<img id="message-delete" class="action-button" src="./icons/delete.png" alt="" width="22" height="22">
</div>
</li>
`;
}
You can transform your string into HTML by writing a function like the one below.
It uses the DOMParser API which creates a new document that will contain all of your HTML you've written in your string.
const parseStringToHTML = (str) => {
const parser = new DOMParser();
return parser.parseFromString(str, 'text/html');
};
And this document works like the one you have in your page, its just a new one that only exists in your JS memory for the time being. Give your string as argument to parseStringToHTML to create HTML.
const message = messageFormatted('This is the message'); // Example message
const messageHTML = parseStringToHTML(message); // Returns a document object with all the features a document object has.
So now that your string is a document you can use methods on it like getElementById, querySelector, etc. But first you must select the element that you need.
const messageDeleteButton = messageHTML.querySelector("#message-delete");
See that I've used messageHTML instead of document. In this scenario messageHTML is a document and therefor we can query inside of it. And now you've found your element inside this document you can add an event listener to it.
messageDeleteButton.addEventListener("click", (e) => {
e.parentNode.parentNode.remove();
});
Okay, so now the event listener has been added. All you have to do now is append the HTML you need from the messageHTML into the document of your page. The HTML can be found in the messageHTML.body property.
Now instead of insertAdjacentHTML use insertAdjacentElement to insert the element in the position of your choosing. The element you want to append is the <li class="message-item"> which would be the firstElementChild in the body of the messageHTML.body property.
So in your submit event listener of your form change the following line from:
messageList.insertAdjacentHTML("beforeend", messageFormatted(messageTextarea.value));
To all that I've explained above.
// Create string
const message = messageFormatted(messageTextarea.value);
// String to document
const messageHTML = parseStringToHTML(message);
// Select delete element.
const messageDeleteButton = messageHTML.querySelector("#message-delete");
// Add event listener to delete element.
messageDeleteButton.addEventListener("click", (e) => {
e.parentNode.parentNode.remove();
});
// Append the <li> element to the messageList.
messageList.insertAdjacentElement("beforeend", messageHTML.body.firstElementChild);
Don't forget to include the parseStringToHTML function in your code. I understand that this is a lot to work out so if you have any question please do ask.
I hope that this will help you out.
Note I see that event delegation (global event listener) is not something you want to do, although as other have stated, it is more performant and easier to implement than other methods, even my own answer. It would also solve listening for elements that you've added or even removed from your list. You could set it even on the <ul id="message-list"> element like T.J. Crowder suggested.
You're on the right track. The identifying characteristic doesn't have to be an id, it can be anything about the element — a class is a common approach.
There are three DOM methods that will help with making the implementation flexible:
closest - starting with the element you call it on, it finds the nearest ancestor matching a given CSS selector
contains - checks to see if a node contains another node
matches - checks to see if the element you call it on matches a given CSS selector
For instance, your current handler requires that the button be the element that was clicked (event.target). That's fine for your img use case, but what if it were a button with an img inside it? Then event.target might be the button of the img, depending on where the user clicked. closest and contains help with that:
document.addEventListener("click", (e) => {
const button = e.target.closest("#message-delete");
if (button) {
button.closest("li").remove();
messageNumber--;
messageEmptyCheck();
}
})
You'll also want to hook the events on the nearest container that will work. Sometimes, you're stuck with it being document or document.body. But oftentimes, you can do better than that. For instance, when listening for button clicks on buttons that are in the cells of a table, you can listen at the table or tbody/thead level rather than document. That's where contains comes into the equation, it makes sure closest didn't go too far up the document tree:
document.querySelector("selector-for-the-table").addEventListener("click", (e) => {
const button = e.target.closest("#message-delete");
if (button && e.currentTarget.contains(button)) {
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
button.closest("li").remove();
messageNumber--;
messageEmptyCheck();
}
})
It's not likely in your case that the search for #message-delete will have gone out of the containing element, so you may not need that bit, but sometimes it can happen and so you want that contains check.
If you really want to add event listener to that element itself then I think you may want to try MutationObserver - https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
You can observe the events with passing config for childList, subtree and on the event you can check whether the element you are looking for has been added or it is there. If you find that you can add the event listener.
You may want to look at following articles -
https://eager.io/blog/three-real-world-use-cases-for-mutation-observer/
https://blog.sessionstack.com/how-javascript-works-tracking-changes-in-the-dom-using-mutationobserver-86adc7446401
I hope it would be helpful.
var topClick = function() {
child = this.parentNode.parentNode.parentNode.parentNode;
child.parentNode.removeChild(child);
var theFirstChild = document.querySelector(".m-ctt .slt");
data[child.getAttribute("data-id")].rank = 5;
insertBlogs();
};
As you see, there is a part of my code like this:
this.parentNode.parentNode.parentNode.parentNode;
Is there another way to optimize the code (without using jQuery)?
You can use a non recursive helper function, for example:
function nthParent(element, n) {
while(n-- && element)
element = element.parentNode;
return element;
}
You can use recursive helper function, for example:
function getNthParent(elem, n) {
return n === 0 ? elem : getNthParent(elem.parentNode, n - 1);
}
var child = getNthParent(someElement, 4);
An alternative approach
Your goal
According to your comments on the original question, your overall goal is this:
There is a list of blogs.Each blog has a button like "edit" and "delete". When I click such buttons I want to find it's blog element.
I believe the best approach to solve the problem you're facing (as opposed to answering the question you asked - sorry!) is to approach the problem in a different manner.
From your comments, you said you have something like this:
<ul id="blog-list">
<li class="blog-item">
<a href="blog1.com">
Boats galore!
</a>
<span class="blog-description">
This is a blog about some of the best boats from Instagram.
</span>
<span class="blog-button delete-button">
Delete
</span>
<span class="blog-button edit-button">
Edit
</span>
</li>
<li class="blog-item">
<a href="blog2.com">
Goats galore!
</a>
<span class="blog-description">
A blog about the everyday adventures of goats in South Africa.
</span>
<span class="blog-button delete-button">
Delete
</span>
<span class="blog-button edit-button">
Edit
</span>
</li>
<li class="blog-item">
<a class="blog-link" href="blog3.com">
Totes galore!
</a>
<span class="blog-description">
A blog about containers and bags, and the owners who love them.
</span>
<span class="blog-button delete-button">
Delete
</span>
<span class="blog-button edit-button">
Edit
</span>
</li>
</ul>
And your goal is to add click event handlers to the button elements for each blog-link item.
So let's just translate that goal from plain English into pseudo-code:
for each `blog-link` `b`
delete_button.onclick = delete_handler(`b`);
edit_button.onclick = edit_handler(`b`);
Example script
Working example at: http://jsfiddle.net/vbt6bjwy/10/
<script>
function deleteClickHandler(event, parent) {
event.stopPropagation();
parent.remove();
}
function editClickHandler(event, parent) {
event.stopPropagation();
var description = parent.getElementsByClassName("blog-description")[0];
description.innerHTML = prompt("Enter a new description:");
}
function addClickHandlers() {
var bloglistElement = document.getElementById("blog-list");
var blogitems = bloglistElement.getElementsByClassName("blog-item");
blogitems.forEach(function(blogitem){
var deleteButtons = blogitem.getElementsByClassName("delete-button");
deleteButtons.forEach(function(deleteButton){
deleteButton.onclick = function(event) {
return deleteClickHandler(event, blogitem);
}
});
var editButtons = blogitem.getElementsByClassName("edit-button");
editButtons.forEach(function(editButton){
editButton.onclick = function(event) {
return editClickHandler(event, blogitem);
}
});
});
}
HTMLCollection.prototype.forEach = Array.prototype.forEach;
addClickHandlers();
</script>
Explanation
The way you've chosen to implement a solution is valid, but I thought I'd give you a different way to look at the same problem.
In my opinion, the inner tags of the blog entity should not have to have knowledge of the structure or properties of the surrounding HTML for your edit and delete buttons to work.
Your original solution has to work backwards from each button up the chain of parents until it finds what it assumes is the correct parent element, based on a brittle method like hard-coding moving up N times in the chain of parent elements. Wouldn't it be nicer if we could use normal JavaScript element selection to be absolutely sure of what we're selecting? That way, no matter how the HTML structure might change, our JavaScript isn't going to break as long as the classes and IDs remain consistent.
This solution iterates over every blog-item in the #blog-list element:
blogitems.forEach(function(blogitem){ ... });
Within the forEach loop, we grab arrays containing .delete-button and .edit-button elements. On each of those elements, we add the appropriate event handler to the onclick property:
deleteButtons.forEach(function(deleteButton){
deleteButton.onclick = function(event) {
return deleteClickHandler(event, blogitem);
}
});
For each deleteButton element in the array, we assign an anonymous function to the event handler onclick. Creating this anonymous function allows us to create a closure.
This means each deleteButton.onclick function will individually "remember" which blogitem it belongs to.
Check out this SO question/answer about closures for more info: How do JavaScript closures work?
And we throw in the HTMLCollection.prototype.forEach... line to provide forEach functionality to all HTMLCollection objects. Functions like getElementsByClassName return an HTMLCollection object. It acts exactly like an array for the most part, but it doesn't let us use forEach by default. A note about compatibility: you can certainly use a standard for loop, since forEach isn't available in all browsers (mostly old IE browsers are to blame). I prefer the forEach idiom.
End result
The end result is a little bit longer code, since the scope of the problem is a little wider than the question you actually asked. The advantage is that this code is much more flexible and resistant to being broken by changes to the HTML structure.
var topClick = function(event){
console.log(event);
child = this.parentNode.parentNode.parentNode.parentNode;
child.parentNode.removeChild(child);
var theFirstChild = document.querySelector(".m-ctt .slt");
data[child.getAttribute("data-id")].rank = 5;
insertBlogs();
};
Surprise! I print the event through console.log.
And find a array like this inside the event:
And I find the element that I want :
function(event){
console.log(event.path[4]);
child = event.path[4];
}
and it work! How magic! Maybe event agent is another way!
Thank you all the same for giving answers!
Next time before asking questions I'll think over first! :D
I've built a page with 3 elements, each of which looks like this:
<div class="col-md-4 event-type">
<a href="{{ pathFor 'step2' }}" id="eventchoice" name="eventchoice" value="corporate">
</a>
</div>
I'm trying to pass the value or name or id of the the <a> element on to a collection using the following code:
EventsController.events({
'click #eventchoice' : function(event) {
console.log(event.target.getAttribute("id"));
console.log(event.target.getAttribute("name"));
console.log(event.target.getAttribute("value"));
var eventchoice = event.target.value;
var params = {
eventchoice: eventchoice
}
//Insert Event
Meteor.call('addEvent', params);
FlashMessages.sendSuccess('Event Added');
}
});
I added the console.log's to see if I can get the id/name/value of the <a> element, but the console outputs 'null' for all of these. Therefore, there is nothing to pass to the collection in the eventAdd method.
I don't believe the problem is with the EventsController, the addEvent method or the Events collection. Any ideas how I can pass these values through?
Thank you for your help!
I think there must be something wrong with your controller then, because if you check the Meteorpad here, it works just fine.
Although you might want to use a class instead of an id if you have many similar elements.
There are several ways of solving your problem but the way I consider as "The Meteor Way" is to use a separate template for every choice (or just use #each loop), if you do that your "this" inside the event code will contain the values you need in your scope, so you won't have to rely on the event.target for them.
Okay so, I want to make an OnClick function in JavaScript that makes it so when a user clicks on it, it will change the word. Is there a replaceword() function or something that which will let me do so? I know this is not real code, but for example:
<p>Quickly <span onclick="replaceword('Surf');">Search</span> The Web!</p>
If there is, then can someone tell me also how to reverse the code maybe? So when they click on it the second time, it will change back to "Search"?
If you want to jump between multiple words, you'll need to store them someplace. You could have two words in the sentence, and toggle the visibility of one or the other (which doesn't scale well), or you could even store them as values on an attribute placed on the element itself.
<p>Hello, <span data-values="World,People,Stack Overflow">World</span>.</p>
I have placed all possible values within the data-values attribute. Each distinct value is separated from the other values by a comma. We'll use this for creating an array of values next:
// Leverage event-delegation via bubbling
document.addEventListener( "click", function toggleWords ( event ) {
// A few variables to help us track important values/references
var target = event.target, values = [], placed;
// If the clicked element has multiple values
if ( target.hasAttribute( "data-values" ) ) {
// Split those values out into an array
values = target.getAttribute( "data-values" ).split( "," );
// Find the location of its current value in the array
// IE9+ (Older versions supported by polyfill: http://goo.gl/uZslmo)
placed = values.indexOf( target.textContent );
// Set its text to be the next value in the array
target.textContent = values[ ++placed % values.length ];
}
});
The results:
The above listens for clicks on the document. There are numerous reasons why this is a good option:
You don't need to wait for the document to finish loading to run this code
This code will work for any elements added asynchronously later in the page life
Rather than setting up one handler for each element, we have one handler for all.
There are some caveats; you may run into a case where the click is prevented from propagating up past a particular parent element. In that case, you would want to add the eventListener closer to your target region, so the likeliness that bubbling will be prevented is less.
There are other benefits to this code as well:
Logic is separated from markup
Scale to any number of values without adjusting your JavaScript
A demo is available for your review online: http://jsfiddle.net/7N5K5/2/
No, there isn't any native function, but you can create on your own.
function replaceword(that, word, oword) {
that.textContent = that.textContent == oword ? word : oword;
}
You can call it like this:
<p>Quickly<span onclick="replaceword(this,'Surf','Search');">Search</span>The Web!</p>
Live Demo: http://jsfiddle.net/t6bvA/6
<p id="abc">Hello</p>
<input type="submit" name="Change" onclick="change()">
function change(){
var ab=document.getElementById('abc').value;
ab.innerHTML="Hi, Bye";
}
I think so this should help you, you should go to site such as w3schools.com, its basic and it will answer your doubt
You can try something like this if you wanna use jQuery
http://jsfiddle.net/R3Ume/2/
<script src="//code.jquery.com/jquery-1.10.2.js"></script>
<body>
<p>Hello <a id='name'>John<a></p>
<input id="clickMe" type="button" value="replace" onclick="onClick();" />
<script>
function onClick() {
$('#name').text('world');
}
</script>