input.value is destroyed after swapping <li> items in places - javascript

I have a simple ul with list items that contain few buttons. Two buttons are navigational they suppose to move list items either forward or backward.
The problem is: whenever I click Previous or Next button, the text in the list item disappears. Moreover, additional edit button adds up after each click.
Is there a way to preserve the text in list items after they get swapped in places?
const form = document.querySelector('form');
const ul = document.querySelector('ul');
const listItem = document.querySelector('li');
const addItemInput = document.querySelector('input.add-item-input');
const addItemButton = document.querySelector('button.add-item-button');
const removeItemButton = document.querySelector('button.remove-item-button');
function attach_span(li) {
const span = document.createElement('span');
span.textContent = addItemInput.value;
li.appendChild(span);
}
function attach_br(li) {
const br = document.createElement('br');
li.appendChild(br);
}
function attachButton_previous(li) {
const prev = document.createElement('button');
prev.className = 'previous';
prev.textContent = 'Previous';
if(li.previousElementSibling) li.appendChild(prev);
}
function attachButton_remove(li) {
const remove = document.createElement('button');
remove.className = 'remove';
remove.textContent = 'Remove';
li.appendChild(remove);
}
function attachButton_edit(li) {
const edit = document.createElement('button');
edit.className = 'edit';
edit.textContent = 'Edit';
li.appendChild(edit);
}
function attachButton_next(li) {
const next = document.createElement('button');
next.className = 'next';
next.textContent = 'Next';
li.appendChild(next);
}
function attachListItems(li) {
attach_span(li);
attach_br(li);
attachButton_previous(li);
attachButton_remove(li);
attachButton_edit(li);
attachButton_next(li);
}
addItemButton.addEventListener('click', () => {
const li = document.createElement('li');
ul.appendChild(li);
attachListItems(li);
addItemInput.value = '';
});
removeItemButton.addEventListener('click', () => {
const ul = document.getElementsByTagName('ul')[0];
const li = document.querySelector('li:last-child');
ul.removeChild(li);
});
ul.addEventListener('click', (e) => {
if(e.target.tagName === 'BUTTON' ) {
const button = e.target;
const li = button.parentNode;
const ul = li.parentNode;
const prevEl = li.previousElementSibling;
const nextEl = li.nextElementSibling;
if(button.className === 'remove') {
ul.removeChild(li);
}
if(button.className === 'previous') {
if(prevEl) {
ul.insertBefore(li, prevEl);
}
}
if(button.className === 'next') {
if(nextEl) {
ul.insertBefore(nextEl, li);
}
}
if(button.className === 'edit') {
const span = li.firstElementChild;
const input = document.createElement('input');
input.type = 'text';
input.value = span.textContent;
li.insertBefore(input, span);
li.removeChild(span);
button.className = 'save';
button.textContent = 'Save';
} else {
const input = li.firstElementChild;
const span = document.createElement('span');
span.textContent = input.value;
li.insertBefore(span, input);
li.removeChild(input);
button.className = 'edit';
button.textContent = 'Edit';
}
}
});
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.6.3/css/all.css" integrity="sha384-UHRtZLI+pbxtHCWp1t77Bi1L4ZtiqrqD80Kn4Z8NTSRyMA2Fd33n5dQ8lWUE00s/" crossorigin="anonymous">
<header></header>
<article>
<nav id="main-nav">
<ul>
</ul>
</nav>
<form class="form">
<input class="add-item-input" name="itemInput" type="text" title="Input for menu items">
<button class="add-item-button" type="button">Add item</button>
<button class="remove-item-button" type="button">Remove last item</button>
</form>
</article>
<p id="spitResult"></p>
<!-- <script src="js/functions.js"></script> -->
<script src="js/app.js"></script>

Related

create a Edit Button while manipulating DOM in javascript

I am creating a simple TODO APP, which adds and deletes and edits a task when it's added, I am trying to figure out how to do edit a task. should I create a new P tag and equal it to par.value?
h1.innerText = 'TODO LIST';
document.body.appendChild(h1);
const input = document.createElement('input');
document.body.appendChild(input);
const addBtn = document.createElement('button');
addBtn.innerText = 'Add';
document.body.appendChild(addBtn);
const container = document.createElement('div');
container.innerText = 'Output';
document.body.appendChild(container);
addBtn.addEventListener('click', function(){
const par = document.createElement('p');
par.innerText = input.value;
container.appendChild(par);
const deleteBtn =document.createElement('button');
deleteBtn.innerText = 'Delete';
par.appendChild(deleteBtn);
const editBtn = document.createElement('button');
editBtn.innerText = 'Edit';
par.appendChild(editBtn);
deleteBtn.addEventListener('click', function(){
this.parentElement.remove();
editBtn.addEventListener('click', function(){
})
})
})
There are many ways how you can do this. One way is to use the attribute contenteditable for your paragraph. See this example:
const h1 = document.createElement('h1');
h1.innerText = 'TODO LIST';
document.body.appendChild(h1);
const input = document.createElement('input');
document.body.appendChild(input);
const addBtn = document.createElement('button');
addBtn.innerText = 'Add';
document.body.appendChild(addBtn);
const container = document.createElement('div');
container.innerText = 'Output';
document.body.appendChild(container);
addBtn.addEventListener('click', function() {
const par = document.createElement('p');
par.innerText = input.value;
container.appendChild(par);
const deleteBtn = document.createElement('button');
deleteBtn.innerText = 'Delete';
container.appendChild(deleteBtn);
const editBtn = document.createElement('button');
editBtn.classList.add('edit');
editBtn.innerText = 'Edit';
container.appendChild(editBtn);
deleteBtn.addEventListener('click', function() {
this.parentElement.remove();
editBtn.addEventListener('click', function() {
})
})
})
// Since the selector ".edit" is dynamic, listen to all elements
document.querySelector('*').addEventListener('click', (e) => {
// Return, if the element has not the class "edit"
if (e.target.className !== 'edit') return
// Set attribute to the paragraph
e.target.previousSibling.previousSibling.setAttribute('contenteditable', 'true');
// focus the paragraph
e.target.previousSibling.previousSibling.focus();
});

Trying to create a button with an li and it's not working

I'm trying to make a grocery list app using an array similar to a todo list. I've got it working with an add button, and had a remove button that would remove an item from the list. But now I'm trying to make it so that a remove button is created with each li so that each grocery item can be selectively removed. I gave it a shot but I'm not quite sure what I've done wrong here.
let addButton = document.getElementById('add-button');
addButton.addEventListener('click', add);
let addInput = document.getElementById('add-input');
//let removeButton = document.getElementById('remove-button');
//removeButton.addEventListener('click', remove);
let groceryList = [
]
function add() {
groceryInput = addInput.value;
groceryList.push(groceryInput);
addInput.value = '';
displayGroceries();
}
function remove(event) {
let position = event.currentTarget.id;
groceryList.splice(position, 1);
displayGroceries();
}
function displayGroceries() {
let groceryUl = document.getElementById('grocery-ul');
groceryUl.innerHTML = '';
for (let i = 0; i < groceryList.length; i++) {
let groceryLi = document.createElement('li');
groceryLi.innerHTML = groceryList[i];
groceryUl.appendChild(groceryLi);
}
let removeButton = document.createElement('button');
removeButton.innerText = "Remove";
removeButton.addEventListener('click', remove);
removeButton.id = i;
groceryLi.appendChild(removeButton);
}
<div class="container">
<h1>Grocery List</h1>
<input id="add-input" placeholder="Add Groceries" autocomplete="off">
<button id="add-button">Add</button>
<!--<button id="remove-button">Remove</button>-->
<div>
<ul id="grocery-ul"></ul>
</div>
Its not working as groceryLi.appendChild(removeButton) you are calling outside for loop.
You have defined groceryLi using let and let have a block scope.
Moving code to add button inside resolves issue
Find fixed method for displayGroceries as follows
function displayGroceries() {
let groceryUl = document.getElementById('grocery-ul');
groceryUl.innerHTML = "";
for (let i = 0; i < groceryList.length; i++) {
let groceryLi = document.createElement("li");
groceryLi.innerHTML = groceryList[i];
let removeButton = document.createElement("button");
removeButton.innerText = "Remove";
removeButton.addEventListener("click", remove);
removeButton.id = i;
groceryLi.appendChild(removeButton);
groceryUl.appendChild(groceryLi);
}
}
In your example you are calling on the incrementing value of i outside of the scope of its loop.
You can create the button using the same method you are using to create your list item tag, then add the button to the UL element tag using .insertAdjacentElement('beforeend', removeBtn).
Then you can use a removeEl function that looks at the event.target parentNode and firstChild --> li that will contain the grocery item and its remove button to both remove the element from the DOM and the array.
function removeEl(event) {
event.target.parentNode.remove(event.target)
if (groceryList.includes(event.target.parentNode.firstChild.textContent)) {
let k = groceryList.indexOf(event.target.parentNode.firstChild.textContent);
if (k !== -1) {
groceryList.splice(k, 1);
}
}
displayGroceries();
}
//and the for loop that creates the new elements in displayGroceries()
for (let i = 0; i < groceryList.length; i++) {
let groceryLi = document.createElement('LI');
let removeBtn = document.createElement('BUTTON');
groceryUl.classList.add('flex-display')
removeBtn.textContent = `remove ${groceryList[i]}`;
removeBtn.setAttribute('onclick', `removeEl(event)`)
groceryLi.innerHTML = groceryList[i];
groceryUl.appendChild(groceryLi);
groceryLi.insertAdjacentElement('beforeend', removeBtn);
}
let addButton = document.getElementById('add-button');
addButton.addEventListener('click', add);
let addInput = document.getElementById('add-input');
//let removeButton = document.getElementById('remove-button');
//removeButton.addEventListener('click', remove);
let groceryList = [
]
function add() {
groceryInput = addInput.value;
groceryList.push(groceryInput);
addInput.value = '';
displayGroceries();
}
function removeEl(event) {
//this targets the LI element, parent of the button
event.target.parentNode.remove(event.target)
//event.target.parentNode.firstChild.textContent -> the grocery item
if (groceryList.includes(event.target.parentNode.firstChild.textContent)) {
// get the index
let k = groceryList.indexOf(event.target.parentNode.firstChild.textContent);
if (k !== -1) {
groceryList.splice(k, 1);
}
}
displayGroceries();
}
function displayGroceries() {
let groceryUl = document.getElementById('grocery-ul');
groceryUl.innerHTML = '';
for (let i = 0; i < groceryList.length; i++) {
let groceryLi = document.createElement('LI');
let removeBtn = document.createElement('BUTTON');
groceryUl.classList.add('flex-display')
removeBtn.textContent = `remove ${groceryList[i]}`;
removeBtn.setAttribute('onclick', `removeEl(event)`)
groceryLi.innerHTML = groceryList[i];
groceryUl.appendChild(groceryLi);
groceryLi.insertAdjacentElement('beforeend', removeBtn);
}
}
.flex-display {
display: flex;
align-items: start;
flex-direction: column;
}
li {
list-style: none;
}
button {
margin-left: 1rem;
}
<div class="container">
<h1>Grocery List</h1>
<input id="add-input" placeholder="Add Groceries" autocomplete="off">
<button id="add-button">Add</button>
<!--<button id="remove-button">Remove</button>-->
<div>
<ul id="grocery-ul">
</ul>
</div>

how to subtract a number on the second button click?

function createElement(){
const input = document.getElementById("input");
const ul = document.getElementById("ul");
const li = document.createElement("li");
const div = document.createElement("div");
div.innerText = input.value;
const btn = document.createElement("button");
btn.innerText = "add 2";
li.appendChild(div);
li.appendChild(btn);
ul.appendChild(li);
btn.onclick = () => {
let num = parseInt(div.innerText);
num = num + 2;
const roundednum = num.toFixed(2);
div.innerText = roundednum;
btnCLicked = true;
}
}
document.getElementById("btn").addEventListener("click", createElement);
<input type="number" id="input">
<ul id="ul"></ul>
<button id="btn">click me</button>
i have this simple code above that creates some elements, and it will add 2 to the number in the div when button inside the <li> is clicked, which works perfectly
now i want to subtract 2 from the number when button is clicked the second time, so first click will add 2, second click will subtract 2
so i come up with this code which uses a boolean to add and subtract the number which works
let btnCLicked = false;
function createElement(){
const input = document.getElementById("input");
const ul = document.getElementById("ul");
const li = document.createElement("li");
const div = document.createElement("div");
div.innerText = input.value;
const btn = document.createElement("button");
btn.innerText = "add 2";
li.appendChild(div);
li.appendChild(btn);
ul.appendChild(li);
btn.onclick = () => {
if (btnCLicked === false) {
let num = parseInt(div.innerText);
num = num + 2;
const roundednum = num.toFixed(2);
div.innerText = roundednum;
btnCLicked = true;
}else if (btnCLicked === true) {
let num = parseInt(div.innerText);
num = num - 2;
const roundednum = num.toFixed(2);
div.innerText = roundednum;
btnCLicked = false;
}
}
}
document.getElementById("btn").addEventListener("click", createElement);
<input type="number" id="input">
<ul id="ul"></ul>
<button id="btn">click me</button>
but now the problem is that when i create an <li>, and click the button inside to add 2 to the number, btnClicked is now set to true.
so when i create another element and click the button for the first time it will subtract the number because btnClicked is true, which is what i dont want, i want the first click to add and subtract on the second click
how do i solve that problem? im thinking about something like making the boolean only inside each li so each li has an own boolean and it will not effect other elements but idk lol i have no other idea
Move the let btnCLicked = false; into the function. Each invocation of createElement() will create a [closure`]1 since it's child function (click handler) is using variables from parents scope:
const input = document.getElementById("input");
const ul = document.getElementById("ul");
function createElement() {
let btnCLicked = false;
const li = document.createElement("li");
const div = document.createElement("div");
const btn = document.createElement("button");
div.innerText = input.value || 0;
btn.innerText = "add 2";
li.appendChild(div);
li.appendChild(btn);
ul.appendChild(li);
btn.onclick = () => {
let num = Number(div.innerText);
if (btnCLicked === false) {
num += 2;
} else {
num -= 2;
}
div.innerText = num;
btnCLicked = !btnCLicked;
}
}
document.getElementById("btn").addEventListener("click", createElement);
<input type="number" id="input">
<ul id="ul"></ul>
<button id="btn">click me</button>
Another solution: use event delegation and use a data-attribute to track the last action (add or subtract):
document.addEventListener("click", handle);
function createElement() {
document.querySelector("#ul").insertAdjacentHTML("beforeend", `
<li>
<div>${(+document.querySelector("#input").value).toFixed(2)}</div>
<button data-action="add">2</button>
</li>`);
}
function handle(evt) {
const origin = evt.target;
if (origin.id === "btn") {
return createElement();
}
if (origin.dataset.action) {
return addOrSubtract2(origin);
}
}
function addOrSubtract2(fromBttn) {
const parentLi = fromBttn.closest("li");
const add = fromBttn.dataset.action === "add";
const num = +(parentLi.querySelector("div").textContent) + (add ? 2 : -2);
fromBttn.dataset.action = add ? "subtract" : "add";
parentLi.querySelector("div").textContent = num.toFixed(2);
}
[data-action]:before {
content: attr(data-action)' ';
}
<input type="number" id="input" value=0 step="0.1">
<button id="btn">create new</button>
<ul id="ul"></ul>

cancel btn for a contenteditable html option

I'm really new to the contenteditable variable for html and so I want to dig your brains a bit :).
I want to allow users to edit content if they wish to but I'm stuck in the cancel btn option.
How can I retain the previous content if a user clicks the cancel btn? I've been looking at the documentation but struggle to find something. Please see the code below:
const cancelBtn = document.getElementById('cancel-btn');
console.log(editBtn);
function editable() {
const h3 = document.querySelector('h3');
const h4 = document.querySelector('h4');
const p = document.querySelector('p');
h3.className = 'edit';
h4.className = 'edit';
p.className = 'edit';
h3.contentEditable = true;
h4.contentEditable = true;
p.contentEditable = true;
addSaveBtn();
}
function cancelEdit() {
const h3 = document.querySelector('h3');
const h4 = document.querySelector('h4');
const p = document.querySelector('p');
h3.classList.remove('edit');
h4.classList.remove('edit');
p.classList.remove('edit');
h3.contentEditable = false;
h4.contentEditable = false;
p.contentEditable = false;
removeSaveBtn();
}
function addSaveBtn() {
const cardAccess = document.querySelector('.card');
const saveBtn = document.createElement('button');
saveBtn.id = 'edit-save-btn';
saveBtn.textContent = 'Save';
if (document.getElementById('edit-save-btn')) {
return;
} else {
cardAccess.insertAdjacentElement('beforeend', saveBtn);
}
saveBtn.addEventListener('click', () => {
const h3 = document.querySelector('h3');
const h4 = document.querySelector('h4');
const p = document.querySelector('p');
h3.classList.remove('edit');
h4.classList.remove('edit');
p.classList.remove('edit');
h3.contentEditable = false;
h4.contentEditable = false;
p.contentEditable = false;
removeSaveBtn();
});
}
function removeSaveBtn() {
if (!document.getElementById('edit-save-btn')) {
return;
} else {
document.getElementById('edit-save-btn').remove();
}
}
editBtn.addEventListener('click', editable);
cancelBtn.addEventListener('click', cancelEdit);```
Edit:
To add a cancel button:
Create a variable that stores the innerHTML when save was pressed.
Set the innerHTML of the contenteditable element to this variable when the cancel button is pressed.
Full Example:
// Set up the variable
var savedText = content.innerHTML;
save.onclick = ()=>{
// store the current content in savedText
savedText = content.innerHTML;
}
cancel.onclick = ()=>{
// set the new html content to what was stored
content.innerHTML = savedText;
}
<div id="content" contenteditable="true"><h1>Edit Me!</h1></div>
<button id="save">Save</button>
<button id="cancel">Cancel</button>
The contentEditable property takes a string, so you probably want something like this:
function cancelEdit() {
const h3 = document.querySelector('h3');
const h4 = document.querySelector('h4');
const p = document.querySelector('p');
h3.classList.remove('edit');
h4.classList.remove('edit');
p.classList.remove('edit');
h3.contentEditable = "false";
h4.contentEditable = "false";
p.contentEditable = "false";
removeSaveBtn();
}
function editable() {
const h3 = document.querySelector('h3');
const h4 = document.querySelector('h4');
const p = document.querySelector('p');
h3.className = 'edit';
h4.className = 'edit';
p.className = 'edit';
h3.contentEditable = "true";
h4.contentEditable = "true";
p.contentEditable = "true";
addSaveBtn();
}
Example with toggling:
toggle.onclick = ()=>{
content.contentEditable = (!content.isContentEditable).toString();
toggle.textContent = content.isContentEditable ? "Turn Editability Off" : "Turn editability On"
}
<div id="content" contenteditable="true"><h1>Edit Me!</h1></div>
<button id="toggle">Turn Editability Off</button>

Create span element in li with Javascript

I'm working on a to-do list project and when creating a new li I would like it to start with a span containing a "X". I wrote the code below, but instead of a span I get "[object HTMLSpanElement]". Anybody knows how to fix this? Thank you!
var enterItem = document.querySelectorAll("input");
var todoList = document.getElementById("todo-list");
for (var i = 0; i < enterItem.length; i++) {
enterItem[i].addEventListener("keypress", function(key) {
if(key.which === 13){
var newLi = document.createElement("li");
var span = document.createElement("span");
var newItem = this.value;
span.textContent = "X";
newLi.appendChild(document.createTextNode(span + " " + newItem));
todoList.appendChild(newLi);
this.value = "";
}
});
}
You are trying to add an html element in a textNode so it triggers the toString of the element
You need
const todoList = document.getElementById("todo-list");
document.getElementById("inputContainer").addEventListener("keypress", function(e) {
const tgt = e.target;
if (tgt.type === "text" && e.which === 13) {
let newLi = document.createElement("li");
let span = document.createElement("span");
span.classList.add("remove");
let newItem = tgt.value;
span.textContent = "X";
newLi.appendChild(span)
newLi.appendChild(document.createTextNode(" " + newItem));
todoList.appendChild(newLi);
tgt.value = "";
}
});
document.getElementById("todo-list").addEventListener("click", function(e) {
const tgt = e.target;
if (tgt.classList.contains("remove")) {
tgt.closest("li").remove();
}
})
<div id="inputContainer">
<input type="text" />
</div>
<ul id="todo-list"></ul>

Categories

Resources