Trouble with to-do list close button - javascript

I have some code that seems to be working, but in a rather odd fashion. When I first refresh the page I have the close button that seems to work fine, but when I make a new to-do list item the close button seems to cease working and I can't pinpoint why.
let addItem = document.getElementById("submitButton");
let userInput = document.getElementById("toDoInput");
let output = document.getElementById("output");
let toDoItem = document.querySelector(".toDoItem");
let close = document.querySelector(".close");
let toDo = document.querySelector(".todo");
/*User clicked the addItem Button
If there is any text inside the text field then add that text to the todo list */
addItem.addEventListener("click", addToDo);
function addToDo(){
var html = `
<ul class="todo">
<li class="toDoItem">
<p>${userInput.value}</p>
<div class="close">X</div>
</li>
</ul>
`;
output.innerHTML += html;
// Resetting input to blank once a submit button has been added.
userInput.value = '';
}
// Figure out how to make closing functionality simple this implementation
// isn't working
close.addEventListener("click", function(e){
console.log("clicked");
let x = e.target.parentElement;
x.style.display = "none";
e.preventDefault();
});
<header>
<input type="text" placeholder="Enter Item Here..." id="toDoInput">
<button id="submitButton">+</button>
</header>
<section id="output">
<ul class="todo">
<li class="toDoItem">
<p>Clean Room!</p>
<div class="close">X</div>
</li>
</ul>
</section>
<script src="todo.js"></script>
I'm also not sure if I'm using best practice as I'm new to web development, so any tips would be thoroughly appreciated as well!

You need a live event handler on your close button(s). This example should help. To offer something more, it's easier and more straight forward to use jQuery for it if you can and don't mind using a JS library.
jQuery example:
$(document).on("click", ".close", function() {
$(this).parent().hide();
});
No need to prevent default behavior since it's a div.

The issue here is that when you re-render the content of the "output" section you lose the event listener bound to the original ".close" element. A few options to work around the issue, have a look at this thread for some examples.

You got pretty close man, and you definitely do not need jQuery.
As you can see below, you don't need to push the <ul> dynamically. It will never change!
<header>
<input type="text" placeholder="Enter Item Here..." id="toDoInput">
<button id="submitButton">+</button>
</header>
<section id="output">
<ul class="todo">
</ul>
</section>
And here is your refactored javascript:
let addItem = document.getElementById("submitButton");
let userInput = document.getElementById("toDoInput");
let output = document.getElementById("output");
let toDoItem = document.querySelector(".toDoItem");
let toDo = document.querySelector(".todo");
/*User clicked the addItem Button
If there is any text inside the text field then add that text to the todo
list */
addItem.addEventListener("click", addToDo);
function addToDo(e){
e.preventDefault();
var html = `<li class="toDoItem">
<p>${userInput.value} </p> <p class="close"
onclick="removeChildElement(this);">X</p>
</li>`;
output.innerHTML += html;
let close = document.querySelector(".close")
// Resetting input to blank once a submit button has been added.
userInput.value = '';
}
// Figure out how to make closing functionality simple this implementation
// isn't working
function removeChildElement(e) {
let x = e.parentElement;
let xParent = x.parentElement;
xParent.removeChild(x);
console.log(xParent);
}
As you can see i made a few changes. Most importantly your close button issue. The function gets the parent on its parent ( ^ 2 ) and then removes its child. Which would be your <li> element!
Enjoy the fiddle: https://jsfiddle.net/fjbyy6uw/35/

Use Event Delegation. Details are commented in Demo. Added a <form> so HTMLFormControlsCollection API can be used, it's simpler, less writing, and I'm lazy.
/* All form controls are referenced by HTMLFormControlsCollection */
var form = document.forms.toDo;
var td = form.elements;
var add = td.add;
var inp = td.input;
var out = td.output;
var toDo = document.querySelector('.toDo');
add.addEventListener("click", addToDo);
/* Limited the dynamically created node to `<li>`. It doesn't make sense to
|| have several `<ul>` having only one `<li>` each.
*/
function addToDo() {
var html = `
<li class="item">
<span>${inp.value}</span>
<b class="close">X</b>
</li>
`;
toDo.innerHTML += html;
}
/* Event Delegation is a way of leveraging event bubbling so
|| that a single ancestor node can be registered to listen for
|| an event (e.currentTarget) and by means event propagation
|| (bubbling) can locate the event origin (node clicked/e.target).
|| In this demo e.currentTarget is output#output and e.target are
|| any b.close. This was possibble by using e.target in conditions
*/
/* removeChild() is used because display:none is not entirely
|| gone. The markup remains just not in the DOM, so it may not
|| look like it's there, under certain conditions a node could be
|| considered present.
*/
out.addEventListener("click", function(e) {
if (e.target !== e.currentTarget) {
if (e.target.className === "close") {
let x = e.target.parentElement
x.parentElement.removeChild(x);
}
}
});
.item {
display: flex;
max-width: 250px;
justify-content: space-between;
}
.item span,
.item b {
display: table-cell;
}
.item b {
cursor: pointer
}
input,
output,
button {
font: inherit
}
<form id='toDo'>
<header>
<input type="text" placeholder="Enter Item Here..." id="input">
<button id="add" type='button'>+</button>
</header>
<output id="output">
<ul class="toDo">
<li class="item">
<span>Clean Room!</span>
<b class="close">X</b>
</li>
</ul>
</output>
</form>

Related

Trying to Append search list to my search input

I am new to HTML ,JavaScript and jQuery. I am currently doing a search box, when I start to type text on the search input the search list must appear and able to click the search list name and append it to search input, and close the search list and left with search input and current text that I clicked on the search list.
var $block = $('.no-results');
$(".personsMenu").hide();
$(".my-textbox").keyup(function() {
var textbox = document.getElementById("textboxEmp");
var val = $(this).val();
var isMatch = false;
var nameAp = document.getElementsByClassName("name12");
$(".personsMenu").show();
if (textbox.value == 0) {
$(".personsMenu").hide();
}
$(".personsMenu div").each(function() {
var content = $(this).html();
if ((content.toLowerCase().indexOf(val) == -1) && (content.toUpperCase().indexOf(val) == -1)) {
$(this).hide();
} else {
isMatch = true;
$(this).show();
}
});
$block.toggle(!isMatch);
});
function mySelect() {
$(".name12").appendTo($(".my-textbox"));
$(".personsMenu").hide();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<div class="cover">
<div name="selected">
<i class="mdi-account-search mdi"></i><input class="my-textbox" id="textboxEmp" autofocus="autofocus" placeholder="search staff member" />
</div>
<div class="personsMenu">
<ul class="infor">
<div class="nm1" name="selected">
<li class="name12" onclick="mySelect()">Malubane Nyikiwe</li>
<li>nyikiwe.malubane#m-t.co.za</li>
</div>
<div class="no-results">no employee found by that name</div>
<div class="nm1" name="selected">
<li class="name12" onclick="mySelect()">Chamano Sydney</li>
<li>sydney.chamano#m-t.co.za</li>
</div>
<div class="nm1" name="selected">
<li class="name12" onclick="mySelect()">Diphofa Tumelo</li>
<li>tumelo.diphofa#m-t.co.za</li>
</div>
</ul>
</div>
</div>
There's several issues in your code which all need to be addressed:
You're using invalid HTML. ul elements can only contain li, not div. I'd suggest restructuring the HTML to use div containers to hold the information for each item in your list.
Use CSS to hide content which should not be visible when the page loads. This avoids the FOUC which can happen as JS only runs after the DOM is ready.
If you've included jQuery in the page, you may as well use it consistently to make your code more succinct.
Use the input method, not keyup, for listening to user input. input will also fire when the user copies content in to the field using the mouse for example, keyup won't.
Use unobtrusive event handlers, eg. jQuery's on() method, not inline onclick attributes. The latter is outdates and bad practice at it doesn't allow for good separation of concerns.
When searching text, equalise the cases of the search and target strings, don't search for both upper and lower versions.
Use text() to search for the content, not html().
To set the value of an input element use val(), not append(). The latter is for adding HTML/text content to an element, not setting its value property.
With all that said, the working code will look something like this:
var $noResults = $('.no-results');
var $names = $(".name12");
var $personsMenu = $('.personsMenu');
var $searchBox = $(".my-textbox").on('input', function() {
var value = $(this).val().trim().toUpperCase();
if (!value) {
$personsMenu.hide();
return;
}
var matches = $personsMenu.show().find('div').each(function() {
var content = $(this).text().toUpperCase();
$(this).toggle(content.indexOf(value) !== -1);
});
$noResults.toggle(matches.filter(':visible').length == 0);
});
$('.item').on('click', function() {
$searchBox.val($(this).find('.name12').text());
$personsMenu.hide();
});
.personsMenu,
.no-results {
display: none;
}
.item {
padding: 10px;
cursor: pointer;
}
.item:hover {
background-color: #CCC;
}
.item p {
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<div class="cover">
<div name="selected">
<i class="mdi-account-search mdi"></i>
<input class="my-textbox" id="textboxEmp" autofocus="autofocus" placeholder="search staff member" />
</div>
<div class="personsMenu">
<div class="no-results">no employee found by that name</div>
<div class="item">
<p class="name12">Malubane Nyikiwe</p>
<p class="email">nyikiwe.malubane#m-t.co.za</p>
</div>
<div class="item">
<p class="name12">Chamano Sydney</p>
<p class="email">sydney.chamano#m-t.co.za</p>
</div>
<div class="item">
<p class="name12">Diphofa Tumelo</p>
<p class="email">tumelo.diphofa#m-t.co.za</p>
</div>
</div>
</div>

Set parent style from child element [duplicate]

This question already has an answer here:
Preventing event bubbling
(1 answer)
Closed 1 year ago.
I'm trying to create a custom dropdown field. Following is my code:
const list = document.querySelector('.list');
const listItems = list.getElementsByTagName('p');
document.querySelector('#category').onclick = function() {
// Able to show the .list DOM element
list.style.display = 'block';
}
Array.from(listItems)
.forEach(function(listItem) {
listItem.onclick = function() {
// Unable to hide the .list DOM element
list.style.display = 'none';
// .listItem's value is getting logged
console.log(listItem.getAttribute('value'));
}
}
);
<div class="dropdown" id="category" name="category">
<div class="trigger">
<p class="selected-category">Category</p>
</div>
<div class="list">
<p value="">None</p>
<p value="1">harum inventore</p>
<p value="2">dolorem voluptatem</p>
<p value="3">dolores consectetur</p>
<p value="4">velit culpa</p>
<p value="5">beatae nulla</p>
</div>
</div>
I'm not exactly sure where it is going wrong as I'm able to set the list's style attribute to to display: block but I'm unable to set it back to display: none even though I'm trying to do the same thing from two different places.
I'm pretty new to Javascript. This might be a duplicate but I honestly tried looking up and trying as many solutions as possible. But none seemed to work. So apologies in advance.
Your code was working right, but because your parent and child had click event listeners on them, you were hiding and showing the list back.
Here you can find more about event bubbling: https://javascript.info/bubbling-and-capturing
const list = document.querySelector('.list');
const listItems = list.getElementsByTagName('p');
// Overlapping event listener 1
document.querySelector('#category').onclick = function() {
list.style.display = 'block';
}
Array.from(listItems)
.forEach(function(listItem) {
// Overlapping event listener 2
listItem.onclick = function(event) {
// to fix double event issue, you have to prevent its bubbling up
event.stopImmediatePropagation();
list.style.display = 'none';
// Uncomment the debugger here and you will see that this code works right
// debugger;
console.log(listItem.getAttribute('data-value'));
}
}
);
<div class="dropdown" id="category" name="category">
<div class="trigger">
<p class="selected-category">Category</p>
</div>
<div class="list">
<p data-value="">None</p>
<p data-value="1">harum inventore</p>
<p data-value="2">dolorem voluptatem</p>
<p data-value="3">dolores consectetur</p>
<p data-value="4">velit culpa</p>
<p data-value="5">beatae nulla</p>
</div>
</div>
The event listener of your parent #category element is executed every time you click one of it's child elements. You can prevent the click event from bubbling up to the parent element by using the stopPropagation() method on the child elements' listener.
Also, as #connexo mentioned in the comments, <p> elements do not have a value attribute. You are probably looking for a <ul> element with <li> children.
Check and run the following Code Snippet for a practical example of the above approach:
const list = document.querySelector('.list');
const listItems = document.querySelectorAll('.list li');
document.querySelector('#category').onclick = function() {
list.style.display = 'block'; // Able to show the .list DOM element
}
Array.from(listItems)
.forEach(function(listItem) {
listItem.onclick = function(e) {
e.stopPropagation(); // prevent parent event listener from being executed
list.style.display = 'none'; // Unable to hide the .list DOM element
}
}
);
.list {display: none; list-style: none;}
<div class="dropdown" id="category" name="category">
<div class="trigger">
<p class="selected-category">Category</p>
</div>
<ul class="list">
<li>None</li>
<li>harum inventore</li>
<li>dolorem voluptatem</li>
<li>dolores consectetur</li>
<li>velit culpa</li>
<li>beatae nulla</li>
</ul>
</div>

Display elements only if they are filled with text, Empty rule not working

I have built a little filter application with a widget. Now I have some filter tags which show you what kind of filter options you have chosen. Now If there is no filter selected the tags must be hidden. I figured out how to do that with a javascript function. The css empty rule does not work with the widget.
But I cannot figure out how to make them visible again.
let divs = document.getElementsByClassName('display');
for (let x = 0; x < divs.length; x++) {
let div = divs[x];
let content = div.innerText.trim();
console.log("Trim Test" + content);
if (content == '') {
div.style.display = 'none';
}
else{
div.style.display = 'inline';
}
}
<div class="tags123">
<span id="display" class="display"> <p id="display1" class="display1"></p></span>
<span id="display" class="display"> <p id="display3" class="display3"></p></span>
<span id="display" class="display"> <p id="display2" class="display2"></p></span>
</div>
The if condition works, my tags are hidden. But the else condition does not seem to trigger If I enter something in the Tags.
I have attached two pictures to show the if condition is working. Do I have to enclose it with some event listener ? Thank you for your time.
It's not quite clear to me how your widget interacts with your markup, but a simple way to toggle the visibility of empty elements would be to filter out the ones with content and use a class that gets toggled on click, like so:
const btn = document.getElementById('btn');
const items = document.getElementsByClassName('foo');
btn.onclick = () => [...items]
.filter(i => !i.textContent)
.forEach(i => i.classList.toggle('hidden'));
.foo::after {
content: 'x';
border-radius: 50%;
background-color: grey;
}
.foo.hidden {
display: none;
}
<button id="btn">toggle visibilty</button>
<div>
<span class="foo">Text</span>
<span class="foo"></span>
<span class="foo"></span>
</div>

How do I add event listeners to dynamically created HTML list elements?

I'm attempting to make a to-do list application where when a clicked, a list element toggles to a class checked that adds a strikethrough style. I'm having trouble targeting one list element at a time and not checking off the whole list. How can I dynamically add event listeners for each item? Currently, with the below code, items added are already checked and I cannot figure out why.
My HTML appears as follows:
<div id="body">
<form id="form" onsubmit="newElement()" target="_self">
<input type="text" id="task" placeholder="What's on the agenda?">
</form>
<ul id="list">
<li class = "checked">test</li >
</ul>
</div>
My function grabs data from a text box through this function
function newElement() {
event.preventDefault(); // stop default redirect
/* create a list item and put the inputted text within */
var li = document.createElement("li"); // create a list item
var inputValue = document.getElementById("task").value; // value of text box
var t = document.createTextNode(inputValue); // create a text node of the box value
li.appendChild(t); // put the text node in the li
li.addEventListener('click', checkToggle(li)); // add event listener for a click to toggle
// a strikethrough
/* append the li item to the ul */
appendItem("list", li);
}
and then the checkToggle function appears as follows
/* make items get checked when clicked */
function checkToggle(li) {
li.classList.toggle("checked");
}
However, when the page is loaded, every element is automatically checked when added already.
You are executing function - checkToggle(li)
You should attach function like
addEventListener('click', checkToggle) without parenthesis
function newElement() {
event.preventDefault(); // stop default redirect
/* create a list item and put the inputted text within */
var li = document.createElement("li"); // create a list item
var inputValue = document.getElementById("task").value; // value of text box
var t = document.createTextNode(inputValue); // create a text node of the box value
li.appendChild(t); // put the text node in the li
li.addEventListener('click', checkToggle); // add event listener for a click to toggle
// a strikethrough
/* append the li item to the ul */
document.querySelector("ul").appendChild(li);
}
function checkToggle() {
this.classList.toggle("checked");
}
.checked{color: blue;}
<div id="body">
<form id="form" onsubmit="newElement()" target="_self">
<input type="text" id="task" placeholder="What's on the agenda?">
</form>
<ul id="list">
<li class = "checked">test</li >
</ul>
</div>

Click event for dynamically generated elements on listview

I tried to apply click event to elements dynamically generated.
This example is work well. http://jsfiddle.net/PzSYM/361/
But my code does not work. http://jsfiddle.net/EurT4/8/
This is same code with above link.
HTML
<ul id="timer_listview" data-role="listview">
<li class="timerList">
<button>on</button>
<span class='status'>play</span>
</li>
<li class="timerList">
<button>on</button>
<span class='status'>play</span>
</li>
<li class="timerList">
<button>on</button>
<span class='status'>play</span>
</li>
</ul> </div>
JavaScript(Jquery)
var counter = 0;
$('#addButton').click(function () {
$('#console').text('new line added');
var text = '<li class="timerList"><button>new' + (++counter) + '</button><span class="status">play</span></li>';
$('#timer_listview').prepend(text);
$('#timer_listview').listview("refresh");
});
var play = 'play';
$('.timerList').on('click', 'button', function () {
$('#console').text('clicked');
var status = $(this).parent().find('span.status');
if (status.text() == play) {
status.text('pause');
$('#console').text('Status Changed');
}
});
CSS
.status {
display: none; }
I got a panic why it does not work. :(
Thank you for reading my question.
p.s Anytime welcome to edit my writing.
UPDATE
I solved this problem. http://jsfiddle.net/EurT4/10/ .
The point is using closest parent.
Thanks to Milind Anantwar.
You are adding elements .timerList dynamically. You need to use:
$('#timer_listview').on('click', '.timerList', function() {
$('#console').text('clicked');
var status = $(this).parent().find('span.status');
if (status.text() == play) {
status.text('pause');
$('#console').text('Status Changed');
}});
<script>
function onclickButton(e)
{
alert(e+"Clicked");
}
</script>
<button onclick="onclickButton(1)">Button1</button>
<button onclick="onclickButton(2)">Button2</button>
<button onclick="onclickButton(3)">Button3</button>
<button onclick="onclickButton(4)">Button4</button>

Categories