I have some To Do-s that are dinamically created by the user:
<div>
<h3 class = 'taskTitle'>do homework </h3>
<p class = 'taskDate'>Expires: 2021.12.31</p>
<input type = button class = 'delBtn' value = 'x'></input>
<input type = button class = 'expandBtn' value = '...'></input>
</div>
<div>
<h3 class = 'taskTitle'>workout </h3>
<p class = 'taskDate'>Expires: 2021.10.11</p>
<input type = button class = 'delBtn' value = 'x'></input>
<input type = button class = 'expandBtn' value = '...'></input>
</div>
**etc.**
On click of the expandBtn a pop up window appears that would contain the title (h3) and the date (p) of the specific To Do.
script:
function showDescription(){
const expandBtns= document.querySelectorAll('.expandBtn')
expandBtns.forEach(btn => {
btn.addEventListener('click', function(event){
let popUp = document.createElement('div')
popUp.classList.add('descriptionBox')
let title = event.target.parentNode.firstChild.textContent **<--says its undefined**
let date = event.target.parentNode.firstChild.textContent **<--says its undefined**
popUp.innerHTML = `
<h3>${title}</h3>
<p class = 'description'> lorem ipsum </p>
<p class = 'dateDescription'>${date}</p>
<input class = 'delDescription' type = button value = 'x'></input>`
const todos = document.querySelector('#todos')
todos.appendChild(popUp)
//close button for popUp
const delDescription = document.querySelectorAll('.delDescription')
delDescription.forEach(btn => {
btn.addEventListener('click', function (event){
event.target.parentNode.remove()
})
})
// alert(document.querySelector('.activeProject').textContent)
})
})
}
So how could I target them? querySelector isn't good either, as I have more than 1 To Do-s. Any help appreciated!
You could add a unique id or class to all the div elements. For example:
<div id="to-do-1"><p>Test</p></div>
<button onclick="myFunction()">Click Me!</button>
<script>
function myFunction() {
document.getElementById("to-do-1").style.display = "inline";
}
</script>
<style>
#to-do-1 {
display: none;
}
</style>
Select all expandBtn
var btn = document.getElementsByClassName("expandBtn")
Create a loop and addEventListener for all of them
for(let i =0; i < btn.length; i++) {
btn[i].addEventListener("click", function () {
let h3 = this.parentNode.getElementsByTagName("h3")[0];
let p = this.parentNode.getElementsByTagName("p")[0];
alert("h3 = "+ h3.innerText + " & p = " + p.innerText);
}
}
now when the user clicking on anyone of them it's will search for h3, p who is belong to the parent of this button
Using .children will get you what you want:
document.addEventListener("click",ev=>{
if (ev.target.className!=="expandBtn") return;
const [name,date]=[...ev.target.parentNode.children].slice(0,2).map(e=>e.textContent)
console.log(name,date);
// further code for popup ...
})
<div>
<h3 class = 'taskTitle'>do homework </h3>
<p class = 'taskDate'>Expires: 2021.12.31</p>
<input type = button class = 'delBtn' value = 'x'></input>
<input type = button class = 'expandBtn' value = '...'></input>
</div>
<div>
<h3 class = 'taskTitle'>workout </h3>
<p class = 'taskDate'>Expires: 2021.10.11</p>
<input type = button class = 'delBtn' value = 'x'></input>
<input type = button class = 'expandBtn' value = '...'></input>
</div>
.firstChild will return the first childNode, which in your case would have been a blank and a line-break and not the <h3> element you expected.
.children on the other hand will return a collection of all child elements (like: <div>, <p>, <span>, ... etc.), the chained .slice(0,2) will slice off the first two elements only.
Related
I created the sort button HTML element in javascript, and the button shows in the console, however, when I apply the sortList.style.display = "block"; to make it display when the add button is clicked the sort button shows as well, not sure why it is not showing.
I do have the sort button hidden in the CSS file, only so that I can have it show when submit event is clicked
const elements = {
form: document.querySelector("#new-task-form"),
input: document.querySelector("#new-task-input"),
list: document.querySelector("#tasks"),
cal: document.querySelector("#calendar"),
sort: document.querySelector(".sort")
elements.list.addEventListener('click',event => {
const {target} = event;
const {id} = target.dataset;
const task = id ? document.querySelector('[data-id="${id}"]'): null;
const sortList = event.target.querySelector(".sort")
for (var i=0; i < sortList.length; i++){
sortList[i].style.display = 'block';
}
}
There is more to the code, however, it's just the sortList.style.display = "block"; I need help with.
Let me know if you need more of the code if the above does not help?
I believe you want sortList to be done as follows:
const sortList = event.target.getElementsByClassName('sort')
for (var i=0; i < sortList .length; i++) {
sortList[i].style.display = 'block';
}
I solved the issue. The issue was that it was not needed to add a display:none in the CSS file because the sort button would appear anyway along with the task list.
as its only created in the tasks.innerHTML.
const tasks = document.createElement("div");
tasks.innerHTML = `
<button class = "sort" >Sort</button>
<div class="task" date-id = "${id}">
<div class="content">
<input type ="checkbox" class="tick">
<input type ="text" class = text id = "text" readonly>${task}
<label class = "due-date" for ="text">${date}</label>
<input type ="date" class = date id = "date">
</div>
<div class = "actions">
<button class="edit" data-id="${id}">Edit</button>
<button class="delete" data-id="${id}">Delete</button>
</div>
</div>
`
I was making a simple to-do list. You submit itens from an input and they go to the To-DO section. When you click over them they go to the 'Done' section. And when you click on them again, they vanish forever. It was all working fine.
But I realized the doneItens array kept growing in length, which I wanted to optimize. So I came up with this line of code
doneItens.splice(i, 1);
which goes inside an onclick event, which you can see in the code inside the deleteDone function.
That gives the error, though,
Error:{
"message": "Uncaught TypeError: doneItens.splice is not a function"
If I put it outside and below the onclick event it also doesn't work. How can I do it?
var input = document.getElementById('play');
var toDo = document.getElementsByTagName('ol')[0];
var done = document.getElementById('done');
function handleSubmit(event) {
event.preventDefault();
const newItem = document.createElement('li');
newItem.setAttribute('class', 'item');
newItem.append(input.value);
toDo.append(newItem);
input.value='';
deleteItem();
}
function deleteItem() {
const toBeDone = document.getElementsByClassName('item');
for(let i = 0; i < toBeDone.length; i++) {
toBeDone[i].onclick = () => {
appendItemDone(toBeDone[i]);
toBeDone[i].style.display = 'none';
deleteDone();
}
}
}
function appendItemDone(item) {
const newDone = document.createElement('li');
newDone.setAttribute('class', 'feito')
newDone.append(item.innerText);
done.append(newDone);
}
function deleteDone() {
const doneItens = document.getElementsByClassName('feito');
console.log('done length', doneItens.length)
for (let i = 0; i < doneItens.length; i++) {
doneItens[i].onclick = () => {
doneItens[i].style.display = 'none';
doneItens.splice(i, 1);
}
}
}
<div id='flex'>
<form class='form' onsubmit='handleSubmit(event)'>
<input placeholder='New item' type='text' id='play'>
<button>Send</button>
</form>
<div id='left'>
<h1 id='todo' >To-do:</h1>
<p class='instruction'><i>(Click over to mark as done)</i></p>
<ol id='here'></ol>
</div>
<div id='right'>
<h1>Done:</h1>
<p class='instruction'><i>(Click over to delete it)</i></p>
<p id='placeholder'></p>
<ol id='done'></ol>
</div>
</div>
With the use of JavaScript DOM API such as Node.removeChild(), Element.remove() and Node.parentNode, your task can be solved with this code:
const input = document.getElementById('play');
const todo = document.getElementById('todo');
const done = document.getElementById('done');
function handleSubmit(event) {
event.preventDefault();
// create new "todo" item
const newTodo = document.createElement('li');
newTodo.textContent = input.value;
todo.append(newTodo);
// clean the input field
input.value = '';
// listen to "click" event on the created item to move it to "done" section
newTodo.addEventListener('click', moveToDone);
}
function moveToDone(event) {
// remove "click"-listener to prevent event listener leaks
event.target.removeEventListener('click', moveToDone);
// move clicked todo-element to "done" section
const newDone = event.target.parentNode.removeChild(event.target);
done.append(newDone);
// listen to "click" event on the moved item to then completely delete it
newDone.addEventListener('click', removeFromDone);
debugElementsLeak();
}
function removeFromDone(event) {
// remove "click"-listener to prevent event listener leaks
event.target.removeEventListener('click', removeFromDone);
// complete remove clicked element from the DOM
event.target.remove();
debugElementsLeak();
}
function debugElementsLeak() {
const todoCount = todo.childElementCount;
const doneCount = done.childElementCount;
console.log({ todoCount, doneCount });
}
<div id="flex">
<form class="form" onsubmit="handleSubmit(event)">
<input placeholder="New item" type="text" id="play">
<button>Add item</button>
</form>
<div id="left">
<h1>To-do:</h1>
<p class="instruction"><em>(Click over to mark as done)</em></p>
<ol id="todo"></ol>
</div>
<div id="right">
<h1>Done:</h1>
<p class="instruction"><em>(Click over to delete it)</em></p>
<p id="placeholder"></p>
<ol id="done"></ol>
</div>
</div>
You'll want to use splice,
and then rather than use hidden, 'refresh' the done element by adding all elements in the spliced array.
I've commented my code where I've made changes and why
var input = document.getElementById('play');
var toDo = document.getElementsByTagName('ol')[0];
var done = document.getElementById('done');
function handleSubmit(event) {
event.preventDefault();
const newItem = document.createElement('li');
newItem.setAttribute('class', 'item');
newItem.append(input.value);
toDo.append(newItem);
input.value='';
deleteItem();
}
function deleteItem() {
const toBeDone = document.getElementsByClassName('item');
for(let i = 0; i < toBeDone.length; i++) {
toBeDone[i].onclick = () => {
appendItemDone(toBeDone[i].cloneNode(true));
toBeDone[i].style.display = 'none';
deleteDone();
}
}
}
function appendItemDone(item) {
const newDone = document.createElement('li');
newDone.setAttribute('class', 'feito')
newDone.append(item.innerText);
done.append(newDone);
}
function deleteDone() {
var doneItens = document.getElementsByClassName('feito');
for (let i = 0; i < doneItens.length; i++) {
doneItens[i].onclick = () => {
var splicedArray = spliceFromArray(doneItens,doneItens[i]);// NEW BIT -CALL NEW SPLICE FUNCTION
done.innerHTML=""; // NEW BIT - SET OVERALL DONE TO BLANK ON DELETE
for(var index in splicedArray){// NEW BIT - fOR EACH RETURNED ELEMENT IN THE SPLICE, ADD IT TO THE OVERALL DONE ELEMENT
done.appendChild(splicedArray[index]);
}
}
}
}
function spliceFromArray(arrayInput,element){// NEW BIT - SPLICE FUNCTION THAT RETURNS SPLICED ARRAY
var array = Array.from(arrayInput);
var index = array.indexOf(element);
if(index!=-1){
if(array.length==1 && index == 0){
array = [];
}
else{
array.splice(index,1);
}
}
return array;
}
<div id='flex'>
<form class='form' onsubmit='handleSubmit(event)'>
<input placeholder='New item' type='text' id='play'>
<button>Send</button>
</form>
<div id='left'>
<h1 id='todo' >To-do:</h1>
<p class='instruction'><i>(Click over to mark as done)</i></p>
<ol id='here'></ol>
</div>
<div id='right'>
<h1>Done:</h1>
<p class='instruction'><i>(Click over to delete it)</i></p>
<p id='placeholder'></p>
<ol id='done'></ol>
</div>
</div>
I want to add html elements to the body of my page as an unordered list. I have used DocumentFragment method to create a fragment of the reply button and comment span. Now I need to add a textbox and a add reply to that ul whenever a user clicks on the reply button and add all the replies as a list next to respective comment. Here is what I've tried:
function comment() {
var my_comment = document.getElementById('comments');
my_comment.innerHTML = "<textarea id='user_comment'> </textarea> <button onclick='addNewItem()'>Post Comment</button>";
}
function addNewItem() {
var thediv = document.getElementById("comments_and_replies");
var listItem = document.createElement("ul");
var replyBox = document.createElement("textbox");
var commentSpan = document.createElement("span");
var user_comment = document.getElementById('user_comment');
var replyButton = document.createElement("button");
listItem.className = "comments-list";
replyButton.innerText = "Reply";
replyButton.className = "reply";
replyButton.addEventListener("click", function() {
var g = document.getElementById('comments_and_replies');
for (var i = 0, len = g.children.length; i < len; i++) {
(function(index) {
g.children[i].onclick = function() {
listItem.insertBefore(replyBox, listItem.children[index]);
}
})(i);
}
})
commentSpan.textContent = user_comment.value;
var documentFragment = document.createDocumentFragment();
documentFragment.appendChild(listItem);
listItem.appendChild(commentSpan);
listItem.appendChild(replyButton);
thediv.appendChild(documentFragment);
}
<section><button onclick="comment()">Leave a comment</button></section>
<div id="comments"></div>
<div id="comments_and_replies"></div>
Event delegation on a single <form> can accommodate an unlimited amount of <button>s even if they are added after the page has loaded.
The example below uses the following:
document.forms
.elements
event.currentTarget
event.target
.matches()
.insertAdjacentHTML()
.previousElementSibling
.parentElement
.remove()
Note: Unless you are submitting data to a server, add type="button" to each <button>
Details are commented in code below
// Refernce <form>
const form = document.forms.commentsReplies;
// Any click on <form> invokes post()
form.onclick = post;
// Pass the event
function post(event) {
/* Reference all <fieldset>
(also <button>, <textarea>, etc) */
const field = event.currentTarget.elements;
// Reference the actual element clicked
const clicked = event.target;
// if element clicked has class postCom
if (clicked.matches('.postCom')) {
/* find <fieldset name="post"> and
insert HTML into it */
field.post.insertAdjacentHTML('beforeEnd', `<fieldset name='commentPost'><textarea></textarea><button class='comTxt' type='button'>Done</button></fieldset>`);
// Otherwise if clicked element has class comTxt
} else if (clicked.matches('.comTxt')) {
/* find the clicked element's element
that is right before it and get it's text */
const text = clicked.previousElementSibling.value;
/* find <fieldset name='comments'> and insert HTML */
field.comments.insertAdjacentHTML('afterBegin', `<fieldset>${text}<button class='postRep' type='button'>Reply</button><ul></ul></fieldset>`);
// Remove <fieldset name='commentPost'>
field.commentPost.remove();
} else if (clicked.matches('.postRep')) {
clicked.insertAdjacentHTML('afterEnd', `<ul><textarea></textarea><button class='repTxt' type='button'>Done</button></ul>`);
} else if (clicked.matches('.repTxt')) {
const text = clicked.previousElementSibling.value;
const list = clicked.parentElement;
list.insertAdjacentHTML('afterBegin', `<li>${text}<button class='postRep' type='button'>Reply</button></li>`);
clicked.previousElementSibling.remove();
clicked.remove();
} else {
return false;
}
}
button {
display: block;
margin-left: 25%;
}
<form id='commentsReplies'>
<fieldset name='post'><button class='postCom' type='button'>Leave a comment</button>
</fieldset>
<fieldset name="comments">
<legend>Comments</legend>
</fieldset>
</form>
I have 2 divs with same class but different textContent
I want to extracts its value using eventListners and pass it as a argument to another function
Html Code
<div class = "seasonDeatils__container">
<p class = "seasonYear ">2020</p>
</div>
<div class = "seasonDeatils__container">
<p class = "seasonYear ">2019</p>
</div>
JavaScript Code I tried
var season;
const getSeasonYear = document.querySelectorAll('.seasonDeatils__container');
getSeasonYear.forEach((el)=>{
el.addEventListener('click', ()=>{
season = el.firstElementChild.textContent;
})
})
//I now want to access the 'season' value elsewhere in the code
Just pass the value of season as an argument from inside your click listener function itself to a function elsewhere in your code as follows:
var season;
const getSeasonYear = document.querySelectorAll('.seasonDeatils__container');
getSeasonYear.forEach((el)=>{
el.addEventListener('click', ()=>{
season = el.firstElementChild.textContent;
someOtherFunction(season);
})
})
someOtherFunction = x => {
console.log(x);
alert(x);
}
<div class = "seasonDeatils__container">
<p class = "seasonYear ">2020</p>
</div>
<div class = "seasonDeatils__container">
<p class = "seasonYear ">2019</p>
</div>
I'm trying to use javascript to create a set of elements over and over again once the user enters a text where it would display the text with a button with an image in it to the side of it but I could not find a way to do it efficiently.
The current method I was going to create would require each element to have a id tag to it so that I could call appendChild to join the elements together.
I also need to have a create element be appended into another create element which adds to the issues
This is what I'm trying to achieve in the html code (the div would not be needed to be created as it is in the html code already)
function addToList(input) {
console.log(x);
let task = document.createElement('p');
task.id = x;
task.appendChild(document.createTextNode(input));
document.getElementById('listOfTasks').appendChild(task);
addCheckBox(x);
x++;
}
function addCheckBox(id) {
let checkBox = document.createElement('a');
checkBox.className = 'button is-rounded is-small';
checkBox.id = 'checkBox';
document.getElementById(id).appendChild(checkBox);
let a = document.createElement('span');
a.className = 'icon is-small';
a.id = 'apple';
document.getElementById(id).appendChild(a);
let b = document.createElement('i');
b.className = 'fas fa-check';
document.getElementById(id).appendChild(b);
}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.min.css"/>
<script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
<div class="container">
<div id="listOfTasks"></div>
</div>
<section class="section">
<div class="container">
<div class="field box form-popup" id="addTask">
<div class="control">
<div class="field is-grouped">
<label class="label"><b>Task to add</b></label>
</div>
<input
type="text"
class="input"
placeholder="Enter Task"
id="task"
required
/>
</div>
<button
type="button submit"
class="button is-success"
id="submit"
onclick="closeForm()"
>
Add
</button>
</div>
</div>
</section>
The current output is shown as
Would be grateful if anyone knows a better method to do this
Make a function that reduces boilerplate code when creating element
function create(name, props, children) {
let elem = document.createElement(name); // create it
const parent = props.parent // we use parent prop elsewhere
let keys = Object.keys(props) // collect keys
keys = keys.filter(function(key) { // remove parent prop from keys
return key !== 'parent'
})
keys.forEach(function(key) { // assign props to element
elem.setAttribute(key, props[key])
})
if (children && children.length) { // add children to element
children.forEach(function(child) {
elem.appendChild(child)
})
}
if (parent) { // attach to parent
document.getElementById(id).appendChild(elem);
}
return elem // return it, to customize further
}
And then
function addCheckBox(id) {
create('a', {
id: 'checkBox', // simple prop
parent: id, // parent prop
class: 'button is-rounded is-small' // simple prop
})
var span = create('span', {
parent: id,
id: 'apple',
class: 'icon is-small'
}, [create('i', { // demo of children
class: 'fa fa-check'
}])
span.setAttribute('data-something', 1) // demo of customizing
}