I have appended 2 buttons for each li element in my app:
todoLi.appendChild(this.createToggleButton());
todoLi.appendChild(this.createDeleteButton());
Later, I added event listeners to trigger when li is moused over and out
todosUl.addEventListener('mouseover', function(event) {
let elementMousedOver = event.target;
if(elementMousedOver.className == 'todoLi') {
elementMousedOver.lastElementChild.style.display = 'inline';
}
});
todosUl.addEventListener('mouseout', function(event) {
let elementMousedOver = event.target;
if(elementMousedOver.className == 'todoLi') {
elementMousedOver.lastElementChild.style.display = 'none';
}
});
Everything works fine for li element, but when I mouse over the buttons, that are children of the li element, event listeners stop working for some reason if the bottons were not children of li element after all.
How can I make appended children to also trigger its parent's event listener?
Here is my app on github: https://mikestepanov.github.io/
repo with files: https://github.com/mikestepanov/mikestepanov.github.io
Events by default are bubbling up the DOM, therefore the event triggered from the li will trigger the ul as well, the issue is that your if statement will handle only the cases in which the event's target is the ul (className == "todoLi")
var todosUl = document.getElementsByClassName("todo")[0];
todosUl.addEventListener('mouseover', function(event) {
let eOver = event.target;
if(eOver.className == 'todo') {
//for the ul
console.log("I'm handeled from " + eOver.className);
} else if (eOver.className == 'item') {
//for the li
console.log("I'm handeled from " + eOver.className);
}
});
todosUl.addEventListener('mouseout', function(event) {
let eOver = event.target;
if(eOver.className == 'todo') {
//for the ul
console.log("I'm handeled from " + eOver.className);
} else if (eOver.className == 'item') {
//for the li
console.log("I'm handeled from " + eOver.className);
}
});
ul{
padding: 15px;
background: lightblue;
}
li{
background: grey;
padding: 5px 5px;
}
<ul class="todo">
<li class="item">Do it!!</li>
</ul>
The problem here lies with the conditional statement. Simply modify your conditional statement to include the button element and it should work.
todosUl.addEventListener('mouseover', function(event) {
let elementMousedOver = event.target;
if(elementMousedOver.className == 'todoLi' ||
elementMousedOver.tagName === "BUTTON") {
elementMousedOver.lastElementChild.style.display = 'inline';
}
});
Related
It is supposed to work like this. After I create an item on my list, it should be marked as done with a line-through after clicked and deleted after the delete button is clicked. All this is fine BUT what I really want is to be able to toggle the item as many times as I want. As you can see when I toggle the item it creates a new delete button instead.
function createListElement() {
var li = document.createElement("li"); // you need to create a node first
li.appendChild(document.createTextNode(input.value)); // then append it to the ul
ul.appendChild(li);
input.value = ""; // clear input after submit
// mark it as done
li.addEventListener("click", function () {
li.classList.toggle("done");
//create delete button
var delete_btn = document.createElement("button");
delete_btn.innerHTML = "Delete";
li.appendChild(delete_btn);
// removes element
delete_btn.addEventListener("click", function () {
li.parentNode.removeChild(li);
});
});
}
I'm adding a codepen so you can take a look at it. Thanks.
Check if there'a already a delete button, and don't add another one if so.
var button = document.getElementById("enter");
var input = document.getElementById("userInput");
var ul = document.querySelector("ul");
// If a button is clicked
button.addEventListener("click", addListAfterClick);
// If input is keypressed
input.addEventListener("keypress", addListAfterKeypress);
function inputLength() {
return input.value.length;
}
// create list and button elements
function createListElement() {
var li = document.createElement("li"); // you need to create a node first
li.appendChild(document.createTextNode(input.value)); // then append it to the ul
ul.appendChild(li);
input.value = ""; // clear input after submit
// mark it as done
li.addEventListener("click", function() {
li.classList.toggle("done");
if (!li.querySelector("button.delete")) {
//create delete button
var delete_btn = document.createElement("button");
delete_btn.innerHTML = "Delete";
delete_btn.classList.add("delete");
li.appendChild(delete_btn);
// removes element
delete_btn.addEventListener("click", function() {
li.parentNode.removeChild(li);
});
}
});
}
function addListAfterClick() {
if (inputLength() > 0) {
createListElement();
}
}
function addListAfterKeypress(event) {
if (inputLength() > 0 && event.key === "Enter") {
createListElement();
}
}
body {
#import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght#300&display=swap');
font-family: 'Plus Jakarta Sans', sans-serif;
}
li {
cursor: pointer;
padding: 5px 0;
}
li>button {
margin-left: 10px;
}
.done {
text-decoration: line-through;
}
<h1>Shopping List</h1>
<p><small>Click item to mark it as <strong>done.</strong></small></p>
<input id="userInput" type="text" placeholder="enter items">
<button id="enter">Enter</button>
<ul>
</ul>
My intention is to make div when keydown, and remove div when the same key is pressed again.
This is my code.
let keydown = false;
document.addEventListener('keydown', function(e) {
if (e.code === 'Space') {
if (!keydown) {
keydown = true;
console.log("space")
e.preventDefault(); //space doesn't manipulate position
$("body").append($("<div id='refactor'></div>"))
$(refactor).append($(".highlight").text())
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
What should I do to remove the div when Space is hit again?
First of all...jquery...avoid this bloatware by all means, please...
Of course this is based on what you've asked here, but I'd recommend instead of store true/false for the pressed key, store the new div element instead. This way, you'll have instant access to it without need search in the DOM.
To remove a node from the DOM, you just need execute node.removeChild(child)
let div = null;
document.addEventListener('keydown', function(e) {
if (e.code === 'Space') {
console.log("space")
e.preventDefault(); //space doesn't manipulate position
if (div)
{
//remove div from DOM
div.parentNode.removeChild(div);
div = null;
}
else
{
//create new div
div = document.createElement("div");
div.id = "refactor";
div.textContent = document.querySelector(".highlight").textContent;
document.body.appendChild(div);
}
}
})
#refactor
{
background-color: lightgreen;
}
<div class="highlight">test</div>
If your whole goal is to show/hide an element, than you should do so via CSS instead of adding/removing elements from DOM, it's significantly faster and allows add additional animations/styles:
let div = document.getElementById("refactor");
document.addEventListener('keydown', function(e) {
if (e.code === 'Space') {
console.log("space")
e.preventDefault(); //space doesn't manipulate position
if (div.classList.contains("hidden"))
{
div.textContent = document.querySelector(".highlight").textContent + " " + Date();
}
div.classList.toggle("hidden");
}
})
#refactor
{
background-color: lightgreen;
transition: height 0.5s, width 0.5s, background-color 0.5s;
height: 1.5em;
width: 100%;
overflow: hidden;
}
#refactor.hidden
{
height: 0;
width: 0;
background-color: pink;
}
<div class="highlight">test</div>
<div id="refactor" class="hidden"></div>
<div>blah</div>
This has got to be really simple, but I just didn't find any answer. I have a simple menu to enter special characters into a search text box. Its doing its job but if i click a character the menu will (blur) and the focus is not returned to the field. As long as the cursor is in the field and/or I click on the characters menu, I want the menu to remain visible. On the event listner for the menu buttons, the first and last lines work. The text is appended to the search box, and hello world logged to the console. But the menu does not show, and the text box does not regain the focus. What is wrong here?
problem demo here
let searchInput = document.querySelector('#search-input');
let charsMenu = document.querySelector('.charsmenu');
// add event listeners to textbox
searchInput.addEventListener('focus', function() {
toggleDisplay(charsMenu, true);
});
searchInput.addEventListener('blur', function() {
toggleDisplay(charsMenu, false);
});
function toggleDisplay(element, show) {
if (show)
element.style.display = 'block';
else
element.style.display = 'none';
}
let listItems = document.querySelectorAll('.charsmenu li');
listItems.forEach((item, index) => {
item.addEventListener('mousedown', (event) => {
searchInput.value += `${event.currentTarget.innerHTML}`;
toggleDisplay(charsMenu, true);
searchInput.focus();
console.log("Hello world!");
});
});
Chances are, after mousedown is fired, the <li> element is going to steal focus away from the input element. Therefore you will want to defer setting focus on the input element at the end of the call stack, i.e.:
window.setTimeout(() => searchInput.focus(), 0);
See proof-of-concept below:
let searchInput = document.querySelector('#search-input');
let charsMenu = document.querySelector('.charsmenu');
// add event listeners to textbox
searchInput.addEventListener('focus', function() {
toggleDisplay(charsMenu, true);
});
searchInput.addEventListener('blur', function() {
toggleDisplay(charsMenu, false);
});
function toggleDisplay(element, show) {
if (show)
element.style.display = 'block';
else
element.style.display = 'none';
}
let listItems = document.querySelectorAll('.charsmenu li');
listItems.forEach((item, index) => {
item.addEventListener('mousedown', (event) => {
searchInput.value += `${event.currentTarget.innerHTML}`;
toggleDisplay(charsMenu, true);
window.setTimeout(() => searchInput.focus(), 0);
});
});
.wrapper {
display: flex;
}
.charsmenu {
display: none;
list-style: none;
}
.charsmenu li {
float: left;
border: 1px solid black;
padding: 16px;
}
<div class="wrapper">
<ul class="charsmenu">
<li>a</li>
<li>b</li>
<li>c</li>
</ul>
<input type="text" id="search-input" />
</div>
As Terry said, the focus is being stolen anyway.
My solution is just add preventDefault() before the focus setting:
listItems.forEach((item, index) => {
item.addEventListener('mousedown', (event) => {
searchInput.value += `${event.currentTarget.innerHTML}`;
toggleDisplay(charsMenu, true);
event.preventDefault();
searchInput.focus();
});
});
By the way, you can have event listener on a <UL> instead of each <LI>.
I want to add a delete button beside each of the items that are to be added.
How to do this properly so the all the functions work?
I have tried the method below as you will see in the code. This seems correct to me but it's not working. This needs to be purely JavaScript`.
var button = document.createElement("BUTTON");
var ul = document.getElementById("list");
var li = document.createElement("li");
function handleAddNewItem() //adds new items and more
{
var item = document.getElementById("input").value;
var ul = document.getElementById("list");
var li = document.createElement("li");
if (item === '') {
alert("Input field can not be empty");
}
else {
button.innerText = "Delete";
li.appendChild(document.createTextNode("- " + item));
ul.appendChild(li);
ul.appendChild(button);
}
document.getElementById("input").value = ""; //clears input
//li.onclick = clearDom;
}//code deletes items by clearDom function
document.body.onkeyup = function (e) //allows items to be added with enter button
{
if (e.keyCode == 13) {
handleAddNewItem();
}
}
function clearDom() {
//e.target.parentElement.removeChild(e.target);//removeChild used
ul.removeChild(li);
ul.removeChild(button);
}
button.addEventListener("click", clearDom);
<body>
<input id="input" placeholder="What needs to be done?">
<button id="add_button" onclick="handleAddNewItem()">ADD</button>
<ul id="list">
</ul>
</body>
<script src="new.js"></script>
</html>
var button = document.createElement("BUTTON");
var ul = document.getElementById("list");
var li = document.createElement("li");
function handleAddNewItem() //adds new items and more
{
var item = document.getElementById("input").value;
var ul = document.getElementById("list");
var li = document.createElement("li");
if (item === '') {
alert("Input field can not be empty");
} else {
button.innerText = "Delete";
li.appendChild(document.createTextNode("- " + item));
ul.appendChild(li);
ul.appendChild(button);
}
document.getElementById("input").value = ""; //clears input
//li.onclick = clearDom;
} //code deletes items by clearDom function
document.body.onkeyup = function(e) //allows items to be added with enter button
{
if (e.keyCode == 13) {
handleAddNewItem();
}
}
function clearDom() {
//e.target.parentElement.removeChild(e.target);//removeChild used
ul.removeChild(li);
ul.removeChild(button);
}
button.addEventListener("click", clearDom);
<input id="input" placeholder="What needs to be done?">
<button id="add_button" onclick="handleAddNewItem()">ADD</button>
<ul id="list">
</ul>
<!-- commented out to reduce errors in the console
<script src="new.js"></script> -->
I am facing this error for now-
"The node to be removed is not a child of this node. at
HTMLButtonElement.clearDom new.js:33:7"
I want to implement the delete button in line with the items listed. so that it deletes the items added one by one separately.
I'd suggest:
function handleAddNewItem() {
/* Move the creation of all variables within the function
in which they're being used: */
const button = document.createElement('button'),
ul = document.getElementById('list'),
li = document.createElement('li'),
item = document.getElementById('input').value;
// here we use String.prototype.trim() to remove leading
// and trailing whitespace from the entered value, to
// prevent a string of white-space (' ') being considered
// valid:
if (item.trim() === '') {
alert("Input field can not be empty");
} else {
button.textContent = "Delete";
// here we again use String.prototype.trim(), this time to
// avoid the creation of a ' task '
// with extraneous white-space:
li.appendChild(document.createTextNode("- " + item.trim()));
// appending the <button> to the <li> instead
// of the <ul> (of which it would be an invalid
// child element anyway):
li.appendChild(button);
ul.appendChild(li);
}
document.getElementById("input").value = ''; //clears input
}
document.body.onkeyup = function(e) //allows items to be added with enter button
{
if (e.keyCode == 13) {
handleAddNewItem();
}
}
// the e - the EventObject - is passed automagically from
// the later use of EventTarget.addEventListener():
function clearDom(e) {
// e.target is the element on which the event that we're
// reacting to was originally fired (the <button>):
const clickedButton = e.target;
// here we use DOM traversal methods to find the closest
// ancestor <li> element, and then use ChildNode.remove()
// to remove it from the DOM:
clickedButton.closest('li').remove();
}
// using event-delegation to catch the
// delete-button clicks:
// first we retrieve the element already on the page which
// will be an ancestor of the appended elements:
document.getElementById('list')
// we then bind the clearDom() function - note the deliberate
// lack of parentheses - as the 'click' event-handler:
.addEventListener('click', clearDom);
function handleAddNewItem() {
/* Creating all variables within the function: */
const button = document.createElement('button'),
ul = document.getElementById('list'),
li = document.createElement('li'),
item = document.getElementById('input').value;
if (item.trim() === '') {
alert("Input field can not be empty");
} else {
button.textContent = "Delete";
li.appendChild(document.createTextNode("- " + item));
li.appendChild(button);
ul.appendChild(li);
}
document.getElementById("input").value = '';
}
document.body.onkeyup = function(e) {
if (e.keyCode == 13) {
handleAddNewItem();
}
}
function clearDom(e) {
const clickedButton = e.target;
clickedButton.closest('li').remove();
}
document.getElementById('list')
.addEventListener('click', clearDom);
<input id="input" placeholder="What needs to be done?">
<button id="add_button" onclick="handleAddNewItem()">ADD</button>
<ul id="list">
</ul>
While this question is already, arguably, already answered, I had a few moments to spare and took advantage of this question to begin learning how to use custom elements. The code, as above, is explained so far as possible using comments in the code itself:
// using an Immediately-Invoked Function
// Expression ('IIFE') to handle the creation of the
// custom element:
(function() {
// creating an HTML <template> element, this could
// instead be placed in, and retrieved from, the DOM:
const template = document.createElement('template');
// using a template literal to create, and format
// the HTML of the created <template> (using a template
// literal allows for new-lines and indentation):
template.innerHTML = `
<style>
*, ::before, ::after {
padding: 0;
margin: 0;
box-sizing: border-box;
}
div.layout {
display: grid;
grid-template-columns: 1fr min-content;
}
div.buttonWrap {
display: flex;
flex-direction: column;
align-items: flex-start;
}
</style>
<div class="layout">
<p></p>
<div class="buttonWrap">
<button>delete</button>
</div>
</div>
`;
// using class syntax:
class TaskItem extends HTMLElement {
// the constructor for the class and, by extension,
// the element that we're defining/creating:
constructor() {
// it seems that super() must be placed as the
// first thing in the constructor function:
super();
// we're holding the contents of the custom
// element in the Shadow DOM, to avoid its
// descendants being affected by CSS in the
// parent page and to prevent JavaScript in
// the document from interacting with the
// contents:
this.attachShadow({
// we want to interact and use elements in
// the Shadow Root, so it must be 'open'
// (although 'closed' is the other valid
// mode-type:
mode: 'open'
});
// here we append the content - not the node
// itself - of the created <template> element
// using Node.cloneNode(), the Boolean true
// means that the descendant elements are also
// cloned and therefore appended:
this.shadowRoot.appendChild(
template.content.cloneNode(true)
);
// for easier reading we cache the shadowRoot
// here (otherwise line-lengths can be a bit
// silly):
const root = this.shadowRoot,
// retrieving the <button> element, which will
// handle the task deletion:
del = root.querySelector('button');
// binding the anonymous function - defined
// using an Arrow function as we don't
// want to change the 'this' in the function -
// as the event-handler for the 'click' event:
del.addEventListener('click', () =>
// here we traverse to the parentNode of
// the 'this', and then use
// parentNode.removeChild() to remove the
// 'this' node:
this.parentNode.removeChild(this));
}
// this callback is executed when the element is
// connected/attached to the DOM:
connectedCallback() {
// we find the Shadow Root:
this.shadowRoot
// find the descendent <p> element:
.querySelector('p')
// and set its text-content to be equal
// to that of the data-task attribute:
.textContent = this.dataset.task;
}
}
// here we define the custom element and its
// class:
window.customElements.define('task-item', TaskItem);
})();
// here we cache a reference to the <button> which will
// cause the addition of new tasks:
const addTask = document.getElementById('add_button'),
// define the function that will handle the
// addition of new tasks:
createTask = () => {
// caching the <input> element:
const taskSource = document.getElementById('input'),
// retrieving and trimming the entered
// <input> value:
task = taskSource.value.trim(),
// creating a new element (custom
// elements are created the same way
// as 'normal' elements):
createdTask = document.createElement('task-item');
// updating the data-task attribute, for
// retrieval/use later when the element
// is added to the DOM:
createdTask.dataset.task = task;
// if we have a task (a zero-length/empty
// string is considered falsey, a string
// with a length greater than zero is
// considered truthy and string with negative
// length is considered impossible (I think),
// and therefore falsey:
if (task) {
// we retrieve the element holding the
// <task-item> elements:
document.getElementById('list')
// and append the created element:
.appendChild(createdTask);
}
// removing the <input> element's value:
taskSource.value = '';
};
// adding createTask() as the event-handler for
// the 'click' event on the <button>:
addTask.addEventListener('click', createTask);
// binding an anonymous function as the handler for
// keyup events on the <body> (binding to a closer
// ancestor would be more sensible in production):
document.body.addEventListener('keyup', (e) => {
// if the e.which is 13 we trust that to be the
// enter key, and then we call createTask()
if (e.which === 13) {
createTask();
}
})
#list {
margin-top: 0.5em;
min-height: 1.5em;
background: transparent radial-gradient(at 0 0, skyblue, lime);
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
grid-gap: 5px;
}
#list:empty::before {
content: 'Add a new task!';
background: transparent linear-gradient(to right, #fffa, #fff0);
padding: 0 0 0 1em;
}
task-item {
border: 2px solid lime;
padding: 0.25em;
background-color: #fff9;
}
<input id="input" class="add_task" placeholder="What needs to be done?">
<button id="add_button" class="add_task">ADD</button>
<div id="list"></div>
JS Fiddle demo.
ChildNode.remove().
Classes.
Constructor.
document.createElement().
document.getElementById().
document.querySelector().
Element.attachShadow().
Event object.
event.target.
EventTarget.addEventListener().
Node.appendChild().
Node.parentNode.
Node.removeChild().
Node.textContent.
super().
Window.customElements.
Not very nice but a solution :)
else {
button.innerText = 'Delete';
li.appendChild(document.createTextNode('- ' + item));
ul.appendChild(li);
let but = button.cloneNode(true); // <-- solution
li.appendChild(but);
// clearDom function
clearDom();
}
And also a function that erases a single entry
function clearDom() {
let buttons = document.querySelectorAll('button:not(#add_button)');
for (let i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', function (e) {
e.target.parentNode.remove();
}, false);
}
}
let button = document.createElement('button');
let ul = document.getElementById('list');
let li = document.createElement('li');
function handleAddNewItem() {
let item = document.getElementById('input').value;
let ul = document.getElementById('list');
let li = document.createElement('li');
if (item === '') {
alert('Input field can not be empty');
}
else {
button.innerText = 'Delete';
li.appendChild(document.createTextNode('- ' + item));
ul.appendChild(li);
let but = button.cloneNode(true);
li.appendChild(but);
clearDom();
}
document.getElementById('input').value = ''; // clears input
}
function clearDom() {
let buttons = document.querySelectorAll('button:not(#add_button)');
for (let i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', function (e) {
e.target.parentNode.remove();
}, false);
}
}
document.body.onkeyup = function (e) {
if (e.keyCode === 13) {
handleAddNewItem();
}
};
<input id="input" placeholder="What needs to be done?">
<button id="add_button" onclick="handleAddNewItem()">ADD</button>
<ul id="list"></ul>
So i am trying to add a couple of css classes (red and shrink) on mouseover and mouseout. I have successfully done that but once I move my mouse out of the container or area of focus, the shrink class still remains.
How can I reset all classes back to the original state?
Here is the code i have so far:
var lis = $('.list');
lis.on('mouseover mouseout', changeColor);
function changeColor (e) {
$el = e.currentTarget;
if( e.type == 'mouseover') {
$el.classList.add('red');
$el.classList.remove('shrink');
} else if (e.type == 'mouseout'){
$el.classList.remove('red');
$el.classList.add('shrink');
}
else {
$el.classList.remove('red');
$el.classList.remove('shrink');
}
}
Thank you.
This might work.
var lis = $('.list');
lis.on('mouseover mouseout', changeColor);
function changeColor (e) {
$el = e.currentTarget;
if( e.type == 'mouseover') {
$el.classList.add('red');
$el.classList.remove('shrink');
} else if (e.type == 'mouseout'){
$el.classList="list";
}
}
.list
{
height:50px;
width:50px;
background:green;
}
.red
{
background : red;
}
.shrink
{
background:yellow;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="list"></div>
Refactored the code using pure jQuery. works as expected.
var lis = $('.list');
lis.hover(function(e){
e.preventDefault();
$(this).toggleClass('red')
.siblings(lis).toggleClass('shrink');
});