these code is working except that for the second or subsequent insert, the message 'Order added' is not appearing. Secondly, after I removed everything using pop and click to view current order, the message 'No customer orders at the moment.' is not appearing even though the code executed.
const order = [];
const customer = {
name: '',
totalCups: 0
}
const checkCustomerOrders = () => {
if (order.length === 0) {
$('.Mesg').show();
$('.Mesg').text("No customer orders at the moment.").fadeTo(4000, 0);
}
}
$('#AllOrders').hide();
$('#btnAdd').click(function () {
var item = $('#customerName');
// Data structure Queue
order.unshift(item.val());
// UX
$('.Mesg').text("Order added").fadeTo(4000, 0);
// UI
var orderElement = $('<div class="orderItem"></div>').text(item.val());
$('#AllOrders').append(orderElement);
// Reset textbox
item.val("");
// Optional Design
$('#ViewAllOrders').click();
debugger;
})
$('#ViewAllOrders').click(function () {
checkCustomerOrders();
$('#AllOrders').show();
$('#CurentOrder').hide();
})
$('#ViewCurrentOrder').click(function () {
debugger;
checkCustomerOrders();
$('#AllOrders').hide();
$('#CurentOrder').show();
var top = order[order.length - 1];
console.log(top);
$('#CurentOrder').empty();
// UI
var orderElement = $('<div></div>').text(top);
$('#CurentOrder').append(orderElement);
})
$('#DeliverOrder').click(function () {
debugger
// Remove one element from array. FIFO.
order.pop();
// Element removed.
// Remove the html element as well
$(".orderItem:first").remove();
// UX
$('.Mesg').text("One customer order delivered").fadeTo(4000, 0);
// Optional Design
$('#ViewAllOrders').click();
})
html{
font-size:1em
}
div.Mesg {
height: 20px !important;
}
ul#menu {
display: flex;
list-style-type: none;
justify-content: space-around;
padding: 0;
margin: 0;
}
ul#menu > li {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<ul id="menu">
<li><a id="ViewAllOrders" href="#">View all orders</a></li>
<li><a id="ViewCurrentOrder" href="#">View current order in process</a></li>
<li><a id="DeliverOrder" href="#">Deliver one order</a></li>
</ul>
<hr />
<div class="Mesg"></div>
<hr />
<div id="AddOrder">
<input id="customerName" type="text" placeholder="Enter customer's name" />
<input id="btnAdd" type="button" value="Add" />
</div>
<div id="AllOrders"></div>
<div id="CurentOrder"></div>
Is it due to fadeTo method?
Change fadeTo() to fadeOut() should fix this
const order = [];
const customer = {
name: '',
totalCups: 0
}
const checkCustomerOrders = () => {
if (order.length === 0) {
$('.Mesg').show();
$('.Mesg').text("No customer orders at the moment.").fadeOut(4000, 0);
}
}
$('#AllOrders').hide();
$('#btnAdd').click(function () {
var item = $('#customerName');
// Data structure Queue
order.unshift(item.val());
// UX
$('.Mesg').text("Order added").fadeOut(4000, 0, function(){
$('.Mesg').text('');
$('.Mesg').show();
});
// UI
var orderElement = $('<div class="orderItem"></div>').text(item.val());
$('#AllOrders').append(orderElement);
// Reset textbox
item.val("");
// Optional Design
$('#ViewAllOrders').click();
debugger;
})
$('#ViewAllOrders').click(function () {
checkCustomerOrders();
$('#AllOrders').show();
$('#CurentOrder').hide();
})
$('#ViewCurrentOrder').click(function () {
debugger;
checkCustomerOrders();
$('#AllOrders').hide();
$('#CurentOrder').show();
var top = order[order.length - 1];
console.log(top);
$('#CurentOrder').empty();
// UI
var orderElement = $('<div></div>').text(top);
$('#CurentOrder').append(orderElement);
})
$('#DeliverOrder').click(function () {
debugger
// Remove one element from array. FIFO.
order.pop();
// Element removed.
// Remove the html element as well
$(".orderItem:first").remove();
// UX
$('.Mesg').text("One customer order delivered").fadeOut(4000, 0);
// Optional Design
$('#ViewAllOrders').click();
})
Related
I have a International country code selector code for my website but the problem I'm facing is that I want to hide the dropdown menu when anyone clicks outside the container, I have already written the code for this feature but the bug I'm facing right now is that It closes the dropdown menu when anyone clicks inside of the container, not the outside. it's doing the exact opposite.
I want to hide the menu when clicked outside.
Please check out my Highlighted code -
// Cache the elements
const xbutton = document.querySelector('.country-code-selector .telcode');
const container = document.querySelector('.country-code-selector .container');
const input = document.querySelector('.country-code-selector input');
const list = document.querySelector('.country-code-selector .list');
// Add event listeners to the button, input, and list
// We use a process called "event delegation" on the list
// to catch events from its children as they "bubble up" the DOM
// https://dmitripavlutin.com/javascript-event-delegation/
xbutton.addEventListener('click', handleButton);
input.addEventListener('input', handleInput);
list.addEventListener('click', handleListClick);
document.addEventListener('click', handleDocumentClick);
// Handles the document click - it checks to see if the clicked
// part of the document has a parent element which is either
// `null` or is the HTML element, and then closes the container
// if it's open
function handleDocumentClick(e) {
const { parentElement } = e.target;
}
/************************************************************************/
/****************** Hide the menu when clicked outside ******************/
/************************************************************************/
document.addEventListener("click", (event) => {
if (container.contains(event.target)) {
if (container.classList.contains('show')) {
container.classList.remove('show');
}
}
});
/************************************************************************/
/****************** Hide the menu when clicked outside ******************/
/************************************************************************/
// All of the data held as objects within an array
const data = [
{ name: 'Afganistan', code: '69', flag: 'afg' },
{ name: 'Barbados', code: '1-246', flag: 'brb' },
{ name: 'Bolivia', code: '591', flag: 'bol' },
{ name: 'Cuba', code: '53', flag: 'cub' },
{ name: 'Fiji', code: '679', flag: 'fji' },
];
// Filters the data based on the characters
// at the start of the provided name
function filterData(data, value) {
return data.filter(obj => {
return (
obj.name.toLowerCase().startsWith(value.toLowerCase())
|| obj.code.toLowerCase().startsWith(value.toLowerCase())
);
});
}
// Create a series of list items based on the
// data passed to it
function createListHtml(data) {
return data.map(obj => {
const { name, code, flag } = obj;
return `
<li
class="item"
data-name="${name}"
data-code="${code}"
data-flag="${flag}"
>
<div class="flag-icon flag-icon-${flag}"></div>
<div class="name">${name} (+${code})</div>
</li>
`;
}).join('');
}
// Toggle the container on/off
function handleButton() {
container.classList.toggle('show');
}
// No data available list item
function createNoDataHtml() {
return '<li class="nodata">No data available</li>';
}
// When the input is changed filter the data
// according to the current value, and then
// create some list items using that filtered data
function handleInput(e) {
const { value } = e.target;
if (value) {
const filtered = filterData(data, value);
if (filtered.length) {
list.innerHTML = createListHtml(filtered);
} else {
list.innerHTML = createNoDataHtml();
}
} else {
list.innerHTML = createListHtml(data);
}
}
// Create some button HTML
function createButtonHtml(code, flag) {
return `
<div class="flag-icon flag-icon-${flag}"></div>
<div class="code">+${code}</div>
`;
}
// Updates the selected list by removing the `selected`
// class from all items, and then adding one to the clicked
// item
function updateSelected(list, item) {
const items = list.querySelectorAll('.item');
items.forEach(item => item.classList.remove('selected'));
item.classList.add('selected');
}
// When an item is clicked, grab the relevant data
// attributes, create the new button HTML, and then
// close the container
function handleListClick(e) {
const item = e.target.closest('li') || e.target;
if (item.classList.contains('item')) {
const { code, flag } = item.dataset;
xbutton.innerHTML = createButtonHtml(code, flag);
updateSelected(list, item);
container.classList.remove('show');
}
}
list.innerHTML = createListHtml(data);
.country-code-selector .telcode {
margin-bottom: 1em;
display: flex;
}
.country-code-selector .telcode div.code, .item div.name {
margin-left: 0.25em;
}
.country-code-selector .container {
display: none;
width: 350px;
border: 1px solid black;
}
.country-code-selector .show {
display: block;
}
.country-code-selector .list {
height: 100px;
list-style: none;
margin: 1em 0 0 0;
padding: 0;
overflow-y: scroll;
border: 1px soldi darkgray;
}
.country-code-selector .item {
display: flex;
padding: 0.25em;
border: 1px solid lightgray;
}
.country-code-selector .item:hover {
cursor: pointer;
}
.country-code-selector .item:hover, .item.selected {
background-color: lightyellow;
}
<link href="https://amitdutta.co.in/flag/css/flag-icon.css" rel="stylesheet"/>
<div class="country-code-selector">
<button type="button" class="telcode">Tel code</button>
<section class="container">
<input type="text" placeholder="Search for country" />
<ul class="list"></ul>
</section>
</div>
I've been stuck on this for a few days. I've tried different selectors and unwrapping the img in the div if that was the problem but no luck. I've been trying to make an accordion.
I'm trying to add a class of "rotate" to the img with the class of "arrow". So that when the question tag is clicked, the arrow img will also rotate.
const questionTag = document.querySelectorAll('.question')
questionTag.forEach(questionTag => {
questionTag.addEventListener('click', () => {
if (questionTag.classList.contains('open')) {
questionTag.classList.remove('open');
} else {
const questionTagOpen = document.querySelectorAll('.open');
questionTagOpen.forEach((questionTagOpen) => {
questionTagOpen.classList.remove('open');
});
questionTag.classList.add('open');
}
});
});
.question + .answer {
display: none;
overflow: hidden;
transition: all ease 1s;
}
.question.open + .answer {
display: block;
}
.arrow.rotate {
transform: rotate(180deg);
}
<div class="container">
<div class="question">How many team members can I invite?
<img class="arrow" src="./images/icon-arrow-down.svg">
</div>
<div class="answer">You can invite up to 2 additional users on the Free plan. There is no limit on
team members for the Premium plan.</div>
</div>
You're missing [0] in your code.
The arrowTag comes from document.querySelectorAll(), which returns a NodeList, you need to specify the element from that NodeList:
var questionTag = document.querySelectorAll('.question')
questionTag.forEach(questionTag => {
questionTag.addEventListener('click', () => {
if (questionTag.classList.contains('open')) {
questionTag.classList.remove('open');
} else {
const questionTagOpen = document.querySelectorAll('.open');
questionTagOpen.forEach((questionTagOpen) => {
questionTagOpen.classList.remove('open');
});
questionTag.classList.add('open');
}
});
var arrowTag = document.querySelectorAll('img.arrow')
questionTag.addEventListener('click', () => {
arrowTag[0].classList.toggle('rotate'); // missing [0] added here
});
});
The addEventListener function is applied to an event target.
Thus, you cannot apply it to the NodeList, which is stored in your arrowTag:
In Laravel 8 app which uses vuejs and jquery
I found wizard made with html like :
<div class="content__inner">
<div class="ccontainer overflow-hiddenn">
<!--multisteps-form-->
<div class="multisteps-form">
<div class="multisteps-form__progress">
<button
class="multisteps-form__progress-btn js-active"
type="button"
title="Add Project Info"
>
Project Info
</button>
<button
class="multisteps-form__progress-btn"
type="button"
title="Add Product"
>
Product Details 22222
</button>
<button
class="multisteps-form__progress-btn"
type="button"
title="Project Budget & Diagram"
>
Project Budget & Diagram
</button>
<button
class="multisteps-form__progress-btn"
type="button"
title="Video & Website Link"
>
Video & Website Link
</button>
</div>
<form class="multisteps-form__form">
<!--single form panel-->
<div
class="multisteps-form__panel shadow p-4 rounded bg-white js-active"
data-animation="scaleIn"
>
<!-- <h3 class="multisteps-form__title">Your User Info</h3> -->
<div class="multisteps-form__content">
<div class="row">
<div class="col-xl-4">
<div class="submit-field">
<h5>
Project Name
<span>*</span>
</h5>
<input
type="text"
class="with-border"
id="name_project"
v-model="project.name"
/>
</div>
</div>
<div class="col-xl-4">
<div class="submit-field">
<h5>
Choose Categories
<span>*</span>
</h5>
<b-form-select
class="tzselectpicker"
v-model="project.category"
:options="project_category"
></b-form-select>
</div>
</div>
and inited js function
setStepForm() {
//DOM elements
const DOMstrings = {
stepsBtnClass: "multisteps-form__progress-btn",
stepsBtns: document.querySelectorAll(
`.multisteps-form__progress-btn`
),
stepsBar: document.querySelector(".multisteps-form__progress"),
stepsForm: document.querySelector(".multisteps-form__form"),
stepsFormTextareas: document.querySelectorAll(
".multisteps-form__textarea"
),
stepFormPanelClass: "multisteps-form__panel",
stepFormPanels: document.querySelectorAll(
".multisteps-form__panel"
),
stepPrevBtnClass: "js-btn-prev",
stepNextBtnClass: "js-btn-next"
};
console.log(" setStepForm DOMstrings::");
console.log(DOMstrings);
//remove class from a set of items
const removeClasses = (elemSet, className) => {
elemSet.forEach(elem => {
elem.classList.remove(className);
});
};
//return exect parent node of the element
const findParent = (elem, parentClass) => {
let currentNode = elem;
while (!currentNode.classList.contains(parentClass)) {
currentNode = currentNode.parentNode;
}
return currentNode;
};
//get active button step number
const getActiveStep = elem => {
return Array.from(DOMstrings.stepsBtns).indexOf(elem);
};
//set all steps before clicked (and clicked too) to active
const setActiveStep = activeStepNum => {
//remove active state from all the state
removeClasses(DOMstrings.stepsBtns, "js-active");
//set picked items to active
DOMstrings.stepsBtns.forEach((elem, index) => {
if (index <= activeStepNum) {
elem.classList.add("js-active");
}
});
};
//get active panel
const getActivePanel = () => {
let activePanel;
DOMstrings.stepFormPanels.forEach(elem => {
if (elem.classList.contains("js-active")) {
activePanel = elem;
}
});
return activePanel;
};
//open active panel (and close unactive panels)
const setActivePanel = activePanelNum => {
//remove active class from all the panels
removeClasses(DOMstrings.stepFormPanels, "js-active");
//show active panel
DOMstrings.stepFormPanels.forEach((elem, index) => {
if (index === activePanelNum) {
elem.classList.add("js-active");
setFormHeight(elem);
}
});
};
//set form height equal to current panel height
const formHeight = activePanel => {
const activePanelHeight = activePanel.offsetHeight;
DOMstrings.stepsForm.style.height = `${activePanelHeight}px`;
};
const setFormHeight = () => {
const activePanel = getActivePanel();
formHeight(activePanel);
};
//STEPS BAR CLICK FUNCTION
DOMstrings.stepsBar.addEventListener("click", e => {
//check if click target is a step button
const eventTarget = e.target;
if (
!eventTarget.classList.contains(
`${DOMstrings.stepsBtnClass}`
)
) {
return;
}
//get active button step number
const activeStep = getActiveStep(eventTarget);
//set all steps before clicked (and clicked too) to active
setActiveStep(activeStep);
//open active panel
setActivePanel(activeStep);
});
//PREV/NEXT BTNS CLICK
DOMstrings.stepsForm.addEventListener("click", e => {
const eventTarget = e.target;
//check if we clicked on `PREV` or NEXT` buttons
if (
!(
eventTarget.classList.contains(
`${DOMstrings.stepPrevBtnClass}`
) ||
eventTarget.classList.contains(
`${DOMstrings.stepNextBtnClass}`
)
)
) {
return;
}
//find active panel
const activePanel = findParent(
eventTarget,
`${DOMstrings.stepFormPanelClass}`
);
let activePanelNum = Array.from(
DOMstrings.stepFormPanels
).indexOf(activePanel);
//set active step and active panel onclick
if (
eventTarget.classList.contains(
`${DOMstrings.stepPrevBtnClass}`
)
) {
activePanelNum--;
} else {
activePanelNum++;
}
setActiveStep(activePanelNum);
setActivePanel(activePanelNum);
setTimeout(() => {
var body = $(".dashboard-content-container");
body.stop().animate(
{ scrollTop: 0 },
500,
"swing",
function() {}
);
}, 100);
});
setTimeout(() => {
setFormHeight();
}, 500);
//SETTING PROPER FORM HEIGHT ONLOAD
window.addEventListener("load", setFormHeight, false);
//SETTING PROPER FORM HEIGHT ONRESIZE
window.addEventListener("resize", setFormHeight, false);
//changing animation via animation select !!!YOU DON'T NEED THIS CODE (if you want to change animation type, just change form panels data-attr)
const setAnimationType = newType => {
DOMstrings.stepFormPanels.forEach(elem => {
elem.dataset.animation = newType;
});
};
//selector onchange - changing animation
const animationSelect = document.querySelector(
".pick-animation__select"
);
if (animationSelect != null) {
animationSelect.addEventListener("change", () => {
const newAnimationType = animationSelect.value;
setAnimationType(newAnimationType);
});
}
},
By clicking on "Next" button next step is opened, but I need to validate inputs before moving to next step.
Please any hints how can I make validation here ?
Also is it some library? I searched in net but drowned...
Thanks!
Each wizard view send wizard position in ajax.In backend based on wizard position validate fields .since you know which and all field exist in each wizard view.
For example consider you have 3 wizard steps.
Step 1 has 3 input fields
Step 2 has 2 input fields
Step 3 has 1 input fields
Suppose if you send current step is 1 then you can only validate those fields.
$validations=[
'step1'=>[
//field validation array
],
'step3'=>[
//field validation array
],
'step3'=>[
//field validation array
],
]
then based on request wizard step you can easily fetch validation rules
Validation::make($validations[$request->step]);
Or you can make all fields validate only if exists in the request
I know there must be a more efficient way of doing this, I've done it this way in the past because I haven't had many buttons to track, but I now have about 40 buttons that each update updates a mysql table with either a yes or no, and having 40 individual variables and equivalent if statements seems like bad code.
Something to note is that you can see the function has a 1 e.g. onclick='btnChange(1, this.value);. There are 7 different buttons, and then these 7 buttons repeat for onclick='btnChange(2, this.value);. So one solution I thought of is to have 7 if statements for each button and have variable names for each if statement and then I would only have to declare a lot of variables. SO I wasn't sure if that was the best way either. Does this make sense?
HTML
<button type="button" name='foo' value="bar1" onclick='btnChange(1, this.value); return false' class='form-control'>Button1</button>
<button type="button" name='hoo' value="bar2" onclick='btnChange(1, this.value); return false' class='form-control'>Button1</button>
JS
var button1YN = 0;
var button2YN = 0;
and so on...
var YNState;
function btnChange(tableid, btnID) {
if (btnID == "bar1") {
if (button1YN === 0) {
YNState = "yes";
button1YN = 1;
} else {
YNState = "no";
buttonY1N = 0;
}
}
if (btnID == "bar2") {
if (button2YN === 0) {
YNState = "yes";
button2YN = 1;
} else {
YNState = "no";
buttonY2N = 0;
}
}
//ajax code to update the mysql table
}
Instead of having a separate variable for each item, create a single variable to represent the state you're attempting to keep track of. This could be an object or an array, depending on your specific needs and/or preferences.
So you might have a state variable that looks like this for example:
// object mapping table names to on/off state
const state = {
tbl1: true,
tbl2: false,
tbl3: true
}
or an array:
const state = [true, false, true];
If you needed something more complex than true or false (on/off) you could use an array of objects:
const state = [
{
table: 't1',
on: true,
lastModified: '2021-03-23',
someOtherThing: 'foo'
},
{
table: 't2',
on: false,
lastModified: '2021-03-23',
someOtherThing: 'bananas'
},
]
Then you just need a function to update the state when something changes. For the simplest case, the true/false array, it could take the index of the item and the new value:
function updateItem(index, newValue) {
state[index] = newValue;
}
Or just toggle the existing true/false value at the given index:
const toggleItem = index => state[index] = !state[index];
Then just call that from your button click handler.
Here's a quick proof of concept:
// initial state. 7 buttons, all off (no value)
const buttons = Array.from({length: 7});
// function to flip the state at the given index
const toggleButton = index => {
buttons[index] = !buttons[index]; // negate existing value. true becomes false, vice-versa
update(); // redraw the dom
}
// redraws the html.
const update = () => {
const container = document.querySelector('.demo');
// just spitting out a button for each item in the array.
// the key thing here is the click handler, which you might
// want to register differently, but this works for
// demonstration purposes.
container.innerHTML = buttons.map((value, index) => `
<button onclick="toggleButton(${index})">
${value ? "✅" : "🔴"} Button ${index}
</button>
`).join('');
}
// do the initial draw
update();
/* purely cosmetic. irrelevant to functionality */
.demo {
display: flex;
flex-direction: column;
width: 100px;
}
button {
border: none;
padding: 0.5em;
border-radius: 4px;
margin: 0.5em;
}
<div class="demo" />
I'm coding a To-Do List in Vanilla Javascript and I want to make it create a new input every time someone presses return on the previous one, which for now is working. I also want to append a checkmark to it, so whenever the task has been finished you can click on it, and it will change the background-color of the input next to which the checkmark is. The only problem is I don't know how to assign some kind of value to each checkmark so the eventListener doesn't always get the first ID selected.
Tried assigning a value to each checkmark and put it into an array, but do not know how to actually assign the exact same value of each checkmark into the array.
let counter, checkmark, cross, list, newRow, addInput, addCheckmark, listid, wrapper, current1;
counter = 1;
checkmark = document.getElementById('checkmark');
cross = document.getElementById('cross');
wrapper = document.querySelector('.to-do-wrapper');
current1 = document.getElementById('current1');
let values = [];
// Event Delegation to listen to all target.matches :)
document.addEventListener('keypress', function(e) {
if (event.target.matches('.input-new-list')) {
let randomVal = Math.floor(Math.random() * 100000);
list = document.querySelector('.list');
newRow = document.createElement("li");
addInput = document.createElement('input');
addCheckmark = document.createElement('i');
addCheckmark.setAttribute('class', 'fas fa-check');
addInput.setAttribute('id', 'current-' + counter)
addInput.setAttribute('class', 'input-new-list');
addInput.setAttribute('type', 'text');
newRow.setAttribute('class', 'new-list');
let key = e.keyCode;
if (key === 13) {
list.appendChild(newRow);
newRow.appendChild(addCheckmark);
addCheckmark.setAttribute('id', 'checkmark');
/* addCheckmark.setAttribute('value', randomVal);
values.push(randomVal); */
newRow.appendChild(addInput);
document.getElementById('current-' + counter).focus();
counter++;
document.querySelector('#default').removeAttribute('placeholder');
}
}
});
// Show edit buttons on click edit list
document.addEventListener('click', function(event) {
list = document.querySelector('.list');
newRow = document.createElement("li");
addInput = document.createElement('input');
addCheckmark = document.createElement('i');
// Ad a random value to checkmark -> Push into array. If event.target.matches checkmark value => execute if
if (event.target.matches('#checkmark')) {
// On click of checkmark, change input background and toggle checkmark color
if (event.target.classList !== 'active') {
checkmark.classList.toggle('active');
if (document.querySelector('.input-new-list')) {
document.querySelector('.input-new-list').classList.toggle('checked');
} else if (document.querySelector('current' + counter)) {
document.querySelector('#current' + counter).classList.toggle('checked')
}
}
}
});
document.addEventListener('mouseover', function() {
if (event.target.matches('input')) {
cross.classList.add('active');
} else if (!event.target.matches('input')) {
cross.classList.remove('active');
}
});
<div class="container-fluid to-do-wrapper" id="toDo">
<ul class="list">
<li class="new-list">
<i class="fas fa-check" id="checkmark"></i>
<input type="text" placeholder="Create a new list" class="input-new-list" id="default" />
<i class="fas fa-times" id="cross"></i>
</li>
</ul>
</div>
Just looking for a way to assign each checkmark and input to its parent li, so doing something on it, wouldn't affect the first selected element but the one being edited.
I kind of went a different direction, storing the data as information in the DOM, and providing a function to generate a JS object that represents todo list data on demand. I used Kamil's CSS in my answer, with some slight changes.
const list = document.querySelector('ul.list');
document.addEventListener('keypress', function(e) {
const eventTarget = e.target;
if (e.keyCode === 13 && eventTarget.parentElement && eventTarget.parentElement.classList.contains('new-item')) {
const clonedListItem = eventTarget.parentElement.cloneNode(true);
clonedListItem.classList.remove('new-item');
const icons = clonedListItem.querySelectorAll('i');
icons.forEach(el => el.classList.remove('hidden'));
const [doneIcon, deleteIcon] = icons;
doneIcon.addEventListener('click', () => toggleDone(clonedListItem));
deleteIcon.addEventListener('click', () => deleteItem(clonedListItem));
list.insertBefore(clonedListItem, eventTarget.parentElement);
eventTarget.removeAttribute('placeholder');
eventTarget.value = '';
}
});
document.getElementById('generateJSON').addEventListener('click', () => {
const data = [...document.querySelectorAll('ul.list li')]
.filter(li => !li.classList.contains('new-item'))
.map(li => ({
text: li.querySelector('input[type="text"]').value,
done: li.classList.contains('done')
}));
console.log('data', data);
});
function toggleDone(item) {
item.classList.toggle('done');
}
function deleteItem(item) {
item.remove();
}
ul {
list-style-type: none;
}
.fas {
cursor: pointer
}
li.done input[type="text"] {
background: #dfd
}
.input-new-list {
margin: 5px
}
li i.fa-check::after {
content: '[+]'
}
li.done i.fa-check::after {
content: '[-]'
}
.hidden {
display: none;
}
<ul class="list">
<li class="new-item">
<i class="fas fa-check hidden"></i>
<input type="text" placeholder="Create a new list" />
<i class="fas hidden" id="cross">x</i>
</li>
</ul>
<input type="button" id="generateJSON" value="Generate JSON" />
You can 'assign value' to checkmark by add/remove class. Don't use same id for many elements. Try to change approach and separate view and data using <template> as follows
let list= [{text:'', done: false}]; // data
function show() {
toDo.innerHTML = list.map((x,i)=> inject(item.innerHTML,{
check: x.done ? 'fa-check' : 'fa-uncheck',
done: x.done ? 'done' : '',
hide: i ? '' : 'hide',
text: x.text,
i,
})).join('');
}
function inject(str, obj) { return str.replace(/\${(.*?)}/g, (x,g)=> obj[g]) }
function check(i) { list[i].done = !list[i].done; show(); }
function change(inp,i) { list[i].text = inp.value; }
function del(i) { list.splice(i,1); show(); }
function newItem(i) {
list.splice(i+1,0,{text:'',done: false});
show();
this['inp'+(i+1)].focus();
}
show();
ul{ list-style-type: none; }
.fas { cursor: pointer }
.done { background: #dfd }
.input-new-list {margin: 5px}
.fa-check::after { content: '[+]'}
.fa-uncheck::after { content: '[-]'}
.hide { display: none }
Click on left '[-]' to mark task as done, type enter to add new, click 'x' to delete
<div class="container-fluid to-do-wrapper">
<ul class="list" id="toDo"></ul>
</div>
<template id="item">
<li class="new-list">
<i class="fas ${check}"
onclick="check(${i})"></i>
<input type="text" placeholder="Create a new list"
class="input-new-list ${done}"
value="${text}" id="inp${i}"
onchange="newItem(${i})"
oninput="change(this, ${i})"/>
<i class="fas fa-times ${hide}" onclick="del(${i})">x</i>
</li>
</template>