submit-form-loading - challenged - javascript

Trying to on submit/button generate loading (animation), bootstrap/js. Kicking my ... , any assist would be appreciated. Assume the following, yes i have tried many variations, the JS is 'calling an api for data' on user submit, the data is rendered in a container call namesContainer, it all works [except] the loading, while rendering. Any assist would be great, an actual good link/bootstrap or other.
<button class="btn btn-primary" type="submit" id="generateNamesButton">
<span id="generateNamesText">Generate Names</span>
<span id="generateNamesSpinner" style="display: none">
<span class="spinner-grow spinner-grow-sm" role="status" aria-hidden="true"></span>
<span class="visually-hidden">Loading...</span>
</span>
</button>
document.querySelector("form").addEventListener("submit", async function(event) {
event.preventDefault();
const inputIndustry = document.querySelector("#inputIndustry").value;
const languageSelect = document.querySelector("#languageSelect");
const selectedLanguage = languageSelect.options[languageSelect.selectedIndex].value;
showLoader(true);
try {
const names = await generateNames(inputIndustry, selectedLanguage);
const namesContainer = document.querySelector("#namesContainer");
const card = namesContainer.parentElement;
card.style.display = "block";
namesContainer.innerHTML = `<ul>${names.split("\n").map(name => `<li>${name}</li>`).join("")}</ul>`;
} catch (error) {
console.error(error);
const namesContainer = document.querySelector("#namesContainer");
namesContainer.innerHTML = "Error generating names";
}
showLoader(false);
});
function showLoader(show) {
const button = document.querySelector("#generateNamesButton");
if (show) {
button.disabled = true;
button.innerHTML = `<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
<span class="visually-hidden">Loading...</span>`;
} else {
button.disabled = false;
button.innerHTML = `<span id="generateNamesText">Generate Names</span>
<span id="generateNamesSpinner" style="display: none;">
<span class="spinner-grow spinner-grow-sm" role="status" aria-hidden="true"></span>
<span class="visually-hidden">Loading...</span>
</span>`;
}
}
function showLoader(show) {
const button = document.querySelector("#generateNamesButton");
if (show) {
button.disabled = true;
button.innerHTML = `<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
<span class="visually-hidden">Loading...</span>`;
} else {
button.disabled = false;
button.innerHTML = `<span id="generateNamesText">Generate Names</span>
<span id="generateNamesSpinner" style="display: none;">
<span class="spinner-grow spinner-grow-sm" role="status" aria-hidden="true"></span>
<span class="visually-hidden">Loading...</span>
</span>`;
}
}
[button=submit], [loading/visible], results rendered [loading/hidden]..

So basically what you mean is that you have a button that when clicked submits some data to the form element. While the onSubmit() is being executed, you need to display a "Loading..." animation on your submit button
To achieve this, follow this approach:
<button id="submitButton" onClick="handleSubmit()">Click Me </button>
let isLoading = false;
const toggleLoading = () => {
isLoading = !isLoading
if(isLoading) {
document.getElementById("submitButton").innerHTML = "Loading...";
} else {
document.getElementById("submitButton").innerHTML = "Click Me";
}
}
const handleSubmit() = async () => {
toggleLoading()
// Your form processing logic
toggleLoading()
}
Here, toggleLoading() will set loading to true if it's false and false if loading variable is true. You can replace the Loading... text with the bootstrap loading animation code
But please note that changes will be clearly visible only if the processing task is an asynchronous task requiring over 1-2 seconds of time

[CSS]
.custom-btn-color {
background-color: #7668a5;
color: #ffffff;
}
.custom-btn-color:hover {
background-color: #aba4ee;
}
[HTML]
<button class="btn custom-btn-color" id="submitButton" onClick="handleSubmit()">Generate Names</button>
[JS]
let isLoading = false;
const toggleLoading = () => {
isLoading = !isLoading;
if (isLoading) {
document.getElementById("submitButton").innerHTML = '<span class="spinner-grow spinner-grow-sm mr-3" role="status" aria-hidden="true"></span> Loading...';
} else {
document.getElementById("submitButton").innerHTML = 'Generate Names';
}
};
const handleSubmit = async () => {
const form = document.querySelector("form");
form.addEventListener("submit", async function(event) {
event.preventDefault();
const inputIndustry = document.querySelector("#inputIndustry").value;
const languageSelect = document.querySelector("#languageSelect");
const selectedLanguage = languageSelect.options[languageSelect.selectedIndex].value;
toggleLoading();
try {
const names = await generateNames(inputIndustry, selectedLanguage);
const namesContainer = document.querySelector("#namesContainer");
const card = namesContainer.parentElement;
card.style.display = "block";
namesContainer.innerHTML = `<ul>${names.split("\n").map(name => `<li>${name}</li>`).join("")}</ul>`;
} catch (error) {
console.error(error);
const namesContainer = document.querySelector("#namesContainer");
namesContainer.innerHTML = "Error generating names";
} finally {
toggleLoading();
}
});
};

Related

Undo the last click on button using JQuery

I have a code like this -
<button class="btn">New York</button>
<button class="btn">Amsterdam</button>
<button class="btn">New Jersey</button>
<button class="btn">Alabama</button>
<button class="btn">Michigan</button>
<button id="undo" class="undo-selection">Undo last selection</button>
and have below code to check the order at which these buttons are clicked.
var click = 0;
var buttons = document.querySelectorAll('button.btn');
buttons.forEach(button => {
button.addEventListener('click', function(){
var current = button.innerText;
var existingClicks = current.split(" ");
if(existingClicks.length > 1) {
button.innerText += ` & ${++click}`;
}
else {
button.innerText = `clicked = ${++click}`;
}
});
});
By using the Undo button I want to clear last selection made and should be able to select new button. Any help is appreciated.
Maybe something like this:
let click = 0;
const buttons = document.querySelectorAll('button.btn');
const clicks = [];
buttons.forEach((button, index) => {
button.addEventListener('click', function(){
const clicked = clicks.includes(index);
clicks.push(index);
if (!clicked) {
button.innerText = `clicked = ${++click}`;
} else {
button.innerText += ` & ${++click}`;
}
});
});
document.getElementById('undo').addEventListener('click', () => {
if (!clicks.length) {
return;
}
const lastClicked = clicks.pop();
click--;
let text = buttons[lastClicked].innerText.split('&');
text.pop();
text = text.join('&');
buttons[lastClicked].innerText = text || buttons[lastClicked].getAttribute('data-text');
});
<button class="btn" data-text="New York">New York</button>
<button class="btn" data-text="Amsterdam">Amsterdam</button>
<button class="btn" data-text="New Jersey">New Jersey</button>
<button class="btn" data-text="Alabama">Alabama</button>
<button class="btn" data-text="Michigan">Michigan</button>
<button id="undo" class="undo-selection">Undo last selection</button>

JS dynamically created area with buttons is missing when to-do task is edited (and not always)

I wanted to create my own To-do list app and I have a problem when I edit a task the area with three buttons (accept, edit and cancel btns) sometimes goes missing.
I'm trying to figure out what's happening with it in the dev tools and it looks like when there are e.g. two tasks (two li elements) the div area with buttons move from one to another and the second li has two divs and the first li (edited one) it left with no buttons at all.
Here's my code, please excuse any weird things you may find there (I'm not really experienced).
const inputField = document.querySelector('.todo__input')
const addBtn = document.querySelector('.todo__btn')
const ulList = document.querySelector('.todo__list')
const infoParagraph = document.querySelector('.todo__info-paragraph')
const popupDiv = document.querySelector('.pop-up')
const popupAcceptBtn = document.querySelector('.edit-accept')
const popupCancelBtn = document.querySelector('.edit-cancel')
const popupInput = document.querySelector('.edit__input')
const popupInfo = document.querySelector('.edit__info')
let liItem
let acceptButton
let editButton
let cancelButton
let editedTodoTask
let editBtnToBeDisabled
const createLiSpace = () => {
const div = document.createElement('div')
div.classList.add('todo__list-buttons')
liItem.append(div)
acceptButton = document.createElement('button')
acceptButton.classList.add('todo__list-btn')
acceptButton.classList.add('todo__list-btn--accept')
acceptButton.innerHTML = '<i class="fa-solid fa-check"></i>'
editButton = document.createElement('button')
editButton.classList.add('todo__list-btn')
editButton.classList.add('todo__list-btn--edit')
editButton.innerHTML = '<i class="fa-solid fa-pencil"></i>'
cancelButton = document.createElement('button')
cancelButton.classList.add('todo__list-btn')
cancelButton.classList.add('todo__list-btn--cancel')
cancelButton.innerHTML = '<i class="fa-solid fa-xmark"></i>'
div.append(acceptButton, editButton, cancelButton)
acceptButton.addEventListener('click', completeTask)
editButton.addEventListener('click', showPopup)
cancelButton.addEventListener('click', deleteTask)
}
const addTask = () => {
if (inputField.value !== '') {
liItem = document.createElement('li')
liItem.classList.add('todo__list-item')
liItem.textContent = inputField.value
createLiSpace()
ulList.appendChild(liItem)
inputField.value = ''
infoParagraph.textContent = ''
} else if (inputField.value == '') {
infoParagraph.style.color = 'rgb(206, 99, 28)'
infoParagraph.textContent = 'Please type in your task first!'
}
}
const showPopup = e => {
editedTodoTask = e.target.closest('li')
popupDiv.style.visibility = 'visible'
popupInfo.textContent = ''
popupInput.value = editedTodoTask.firstChild.textContent
}
const changeTodoTask = () => {
if (popupInput.value !== '') {
editedTodoTask.textContent = popupInput.value
createLiSpace()
closePopup()
popupInput.value = ''
} else {
popupInfo.textContent = 'Please type in some text here...'
}
}
const enterAddTask = (e) => {
if (e.key == "Enter") {
addTask()
}
}
const completeTask = e => {
editedTodoTask = e.target.closest('li')
editedTodoTask.classList.toggle('crossline')
}
const closePopup = () => {
popupDiv.style.visibility = 'hidden'
}
const deleteTask = e => {
ulList.removeChild(e.target.closest('li'))
}
popupAcceptBtn.addEventListener('click', changeTodoTask)
popupCancelBtn.addEventListener('click', closePopup)
addBtn.addEventListener('click', addTask)
inputField.addEventListener('keyup', enterAddTask)
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css" integrity="sha512-xh6O/CkQoPOWDdYTDqeRdPCVd1SpvCA9XXcUnZS2FmJNp1coAFzvtCN9BmamE+4aHK8yyUHUSCcJHgXloTyT2A==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<div class="container">
<div class="todo-top-section">
<h1 class="todo__header">My to-do list</h1>
<input type="text" placeholder="Type your task here..." class="todo__input">
<button class="todo__btn"><img class="todo__blob" src="/src/img/blobanimation.svg" alt="">add</button>
</div>
<div class="todo-bottom-section">
<h2 class="todo__heading">Task list:</h2>
<p class="todo__info-paragraph">No tasks here yet</p>
<ul class="todo__list">
</ul>
</div>
<div class="pop-up">
<h3 class="edit__header">Want to change anything in here?</h3>
<p class="edit__info"></p>
<div class="edit-container">
<input type="text" class="edit__input" placeholder="Type in your change...">
<div class="todo__list-buttons">
<button class="todo__list-btn todo__list-btn--accept edit-accept">Accept <i class="fa-solid fa-check"></i></button>
<button class="todo__list-btn todo__list-btn--cancel edit-cancel">Cancel <i class="fa-solid fa-xmark"></i></button>
</div>
</div>
</div>
Could you please check and specify what is causing that trouble?
You can find my code here too: https://codepen.io/re_aleksandra/pen/PoebEPv
Thank you!

I put <form> tag in my template into a for loop to show 5-star-rating for each image and user can rate but its only saving response of 1st element

i try to make a website with different images and user can give 5=-star-rating to them so i save the images from admin side and place a card into loop to show all the present data from database and with that i also place 5-star- rate to give rating but it saving the data of 1st image only in the card i want it to save rating of each image, i search it everywhere but cant find any solution please help me with this.
This is my HTML page
<div class="container">
<div class="row">
{%for a in ab%}
<div class="col-4">
<div class="card">
<img class="card-img-top" src="{{a.image.url}}">
<div class="card-body">
<h5 class="card-title">{{a.product}}</h5>
<div class="col text-center">
<form class="rate-form" action="" method="POST" id="{{a.id}}">
{% csrf_token %}
<button type="submit" class="fa fa-star fa-3x my-btn" id="first"></button>
<button type="submit" class="fa fa-star fa-3x my-btn" id="second"></button>
<button type="submit" class="fa fa-star fa-3x my-btn" id="third"></button>
<button type="submit" class="fa fa-star fa-3x my-btn" id="fourth"></button>
<button type="submit" class="fa fa-star fa-3x my-btn" id="fifth"></button>
</form>
<br>
<div id="confirm-box"></div>
</div>
</div>
</div>
{%endfor%}
</div>
</div>
This is my java-script
// console.log('hello world')
// get all the stars
const one = document.getElementById('first')
const two = document.getElementById('second')
const three = document.getElementById('third')
const four = document.getElementById('fourth')
const five = document.getElementById('fifth')
// get the form, confirm-box and csrf token
const form = document.querySelector('.rate-form')
const confirmBox = document.getElementById('confirm-box')
// const csrf = document.getElementsByName('CsrfViewMiddleware')
var csrf = $("input[name=csrfmiddlewaretoken]").val();
const handleStarSelect = (size) => {
const children = form.children
console.log(children[0])
for (let i=0; i < children.length; i++) {
if(i <= size) {
children[i].classList.add('checked')
} else {
children[i].classList.remove('checked')
}
}
}
const handleSelect = (selection) => {
switch(selection){
case 'first': {
// one.classList.add('checked')
// two.classList.remove('checked')
// three.classList.remove('checked')
// four.classList.remove('checked')
// five.classList.remove('checked')
handleStarSelect(1)
return
}
case 'second': {
handleStarSelect(2)
return
}
case 'third': {
handleStarSelect(3)
return
}
case 'fourth': {
handleStarSelect(4)
return
}
case 'fifth': {
handleStarSelect(5)
return
}
default: {
handleStarSelect(0)
}
}
}
const getNumericValue = (stringValue) =>{
let numericValue;
if (stringValue === 'first') {
numericValue = 1
}
else if (stringValue === 'second') {
numericValue = 2
}
else if (stringValue === 'third') {
numericValue = 3
}
else if (stringValue === 'fourth') {
numericValue = 4
}
else if (stringValue === 'fifth') {
numericValue = 5
}
else {
numericValue = 0
}
return numericValue
}
if (one) {
const arr = [one, two, three, four, five]
arr.forEach(item=> item.addEventListener('mouseover', (event)=>{
handleSelect(event.target.id)
}))
arr.forEach(item=> item.addEventListener('click', (event)=>{
// value of the rating not numeric
const val = event.target.id
let isSubmit = false
form.addEventListener('submit', e=>{
e.preventDefault()
if (isSubmit) {
return
}
isSubmit = true
// picture id
const id = e.target.id
// value of the rating translated into numeric
const val_num = getNumericValue(val)
$.ajax({
url: '/add_rating/',
type: 'post',
data: {
'csrfmiddlewaretoken': csrf,
'el_id': id,
'val': val_num,
},
success: function(response){
console.log(response)
confirmBox.innerHTML = `<h1>Successfully rated with ${response.score}</h1>`
},
error: function(error){
console.log(error)
confirmBox.innerHTML = '<h1>Ups... something went wrong</h1>'
}
})
})
}))
}
jQuery(document).ready(function($){
$(".btnrating").on('click',(function(e) {
var previous_value = $("#selected_rating").val();
var selected_value = $(this).attr("data-attr");
$("#selected_rating").val(selected_value);
$(".selected-rating").empty();
$(".selected-rating").html(selected_value);
for (i = 1; i <= selected_value; ++i) {
$("#rating-star-"+i).toggleClass('btn-warning');
$("#rating-star-"+i).toggleClass('btn-default');
}
for (ix = 1; ix <= previous_value; ++ix) {
$("#rating-star-"+ix).toggleClass('btn-warning');
$("#rating-star-"+ix).toggleClass('btn-default');
}
}));
});

Event handlers fires multiple times when triggered

For some reasons unbeknownst to me, the events for elements I created dynamically fires multiple times (mulitplying itself by three each time).
Those, I added manually fire only ones. As you can see, I have some console.log() which I use to track if the function was called multiple times or it was the event handler that fired multiple times. Also added event.stopPropagation(), still fires.
here is my html code
{% extends "mail/layout.html" %}
{% load static %}
{% block body %}
<h2>{{ request.user.email }}</h2>
<button class="btn btn-sm btn-outline-primary" id="inbox">Inbox</button>
<button class="btn btn-sm btn-outline-primary" id="compose">Compose</button>
<button class="btn btn-sm btn-outline-primary" id="sent">Sent</button>
<button class="btn btn-sm btn-outline-primary" id="archived">Archived</button>
<a class="btn btn-sm btn-outline-primary" href="{% url 'logout' %}">Log Out</a>
<hr>
<div id="emails-view">
</div>
<div id="mail-template">
</div>
<div id="compose-view">
<h3>New Email</h3>
<form id="compose-form" action="#">
<div class="form-group">
From: <input disabled class="form-control" value="{{ request.user.email }}">
</div>
<div class="form-group">
To: <input id="compose-recipients" class="form-control" placeholder="Recipient">
</div>
<div class="form-group">
<input class="form-control" id="compose-subject" placeholder="Subject" value="HOMES">
</div>
<p><textarea class="form-control" id="compose-body" placeholder="Body"></textarea></p>
<p><input value="Send" type="submit" class="btn btn-primary" id='compose-submit'/></p>
<!--<p><button class="btn btn-primary" id="compose-submit">Send</button></p>-->
</form>
</div>
{% endblock %}
{% block script %}
<script src="{% static 'mail/index.js' %}" type='text/javascript'></script>
{% endblock %}
here is my Javascript file
function check_parent(e){
return e.id || e.parentNode.id || e.parentNode.parentNode.id;
}
function read_unread(mail_id, action){
let todo = (action == 'read') ? true : false;
fetch(`emails/${mail_id}`,{
method: 'PUT',
body: JSON.stringify({
read: todo
})
})
}
// This one has an event that fires multiple times
function open_email(mail_id) {
document.querySelector('#emails-view').style.display = 'none';
document.getElementById('mail-template').style.display = 'block';
document.querySelector('#compose-view').style.display = 'none';
let mail_template = document.getElementById('mail-template')
fetch(`emails/${mail_id}`).then(
response => response.json())
.then(result =>
{
let mail = `<div><strong>From</strong>: ${result['sender']}</div>`;
mail += `<div><strong>To</strong>: ${result['recipients']}</div>`;
mail += `<div><strong>Subject</strong>: ${result['subject']}</div>`;
mail += `<div><strong>Timestamp</strong>: ${result['timestamp']}</div>`;
mail += '<p><input value="Reply" type="submit" class="btn btn-sm btn-outline-primary" id="reply"/></p>'
mail += '<hr>'
mail += `${result['body']}`
mail_template.innerHTML = mail;
mail_template.addEventListener('click', e => {
if (e.target.id === 'reply'){
e.stopPropagation();
e.preventDefault();
let tops = `${result['sender']}`;
compose_email(tops);
}
});
})
read_unread(mail_id, 'read');
}
document.addEventListener('DOMContentLoaded', function() {
// Use buttons to toggle between views
document.querySelector('#inbox').addEventListener('click', () => load_mailbox('inbox'));
document.querySelector('#sent').addEventListener('click', () => load_mailbox('sent'));
document.querySelector('#archived').addEventListener('click', () => load_mailbox('archive'));
document.querySelector('#compose').addEventListener('click', compose_email);
// By default, load the inbox
load_mailbox('inbox');
});
function compose_email(receiver='') {
// Show compose view and hide other views
document.querySelector('#emails-view').style.display = 'none';
document.getElementById('mail-template').style.display = 'none';
document.querySelector('#compose-view').style.display = 'block';
// Clear out composition fields
document.querySelector('#compose-recipients').value = receiver;
document.querySelector('#compose-subject').value = '';
document.querySelector('#compose-body').value = '';
}
document.querySelector('#compose-form').addEventListener('submit', (e) => {
document.querySelector('#compose-recipients').value;
let recipients = document.querySelector('#compose-recipients').value;
let subject = document.querySelector('#compose-subject').value;
let body = document.querySelector('#compose-body').value;
e.preventDefault();
// Send mail to the backend
fetch('/emails', {
method: 'POST',
body: JSON.stringify({
recipients: recipients,
subject: subject,
body: body
})
}).then(response => response.json())
.then(result => {console.log(result)})
load_mailbox('sent')
});
// This one has an event that fires multiple times
function load_mailbox(mailbox) {
console.log('first');
let emails_view = document.querySelector('#emails-view')
// Show the mailbox and hide other views
document.querySelector('#emails-view').style.display = 'block';
document.getElementById('mail-template').style.display = 'none';
document.querySelector('#compose-view').style.display = 'none';
// Show the mailbox name
document.querySelector('#emails-view').innerHTML = `<h3>${mailbox.charAt(0).toUpperCase() + mailbox.slice(1)}</h3>`;
// Retrieve users sent emails
fetch(`/emails/${mailbox}`).then(response => response.json())
.then(emails => {
emails.forEach( email => {
let div = document.createElement('div')
div.className = 'mail-boxs';
//div.href = `emails/${email['id']}`;
if (email['read'] === true){
div.style.backgroundColor = '#DCDCDC';
}else {
div.style.backgroundColor = 'white';
}
div.id = `${email['id']}`;
div.style.border = 'solid 1px gray';
div.style.padding = '8px';
let email_sub = document.createElement('span')
email_sub.className = 'mail-boxs';
email_sub.innerHTML = `<strong class="mail-boxs">${email['sender']}</strong> ${email['subject']}`
let time_stamp = document.createElement('span')
time_stamp.innerHTML = `${email['timestamp']}`
time_stamp.className = 'mail-boxs';
time_stamp.style.float = 'right';
div.appendChild(email_sub);
div.appendChild(time_stamp);
emails_view.appendChild(div)
emails_view.addEventListener('click', e => {
console.log('second');
if (e.target.className == 'mail-boxs'){
let mail_id = check_parent(e.target);
open_email(parseInt(mail_id))
}
})
});
});
}
Edit, parameters get [object MouseEvent] as values of default parameters.
Here is the full code. It is a bit longer, but I have added multi-line comment to the relevant parts vis-à-vis the arbitrary values passed to compose_mail.
let emails_view = document.querySelector('#emails-view')
let mail_template = document.getElementById('mail-template')
function check_parent(e){
return e.id || e.parentNode.id || e.parentNode.parentNode.id;
}
// Read or unread an email
function read_unread(mail_id, action){
let todo = (action == 'read') ? true : false;
fetch(`emails/${mail_id}`,{
method: 'PUT',
body: JSON.stringify({
read: todo
})
})
}
// Archive or unarchive an email
function archive(mail_id, action){
let todo = (action == 'archive') ? true : false;
fetch(`emails/${mail_id}`,{
method: 'PUT',
body: JSON.stringify({
archived: todo
})
})
}
function open_email(mail_id) {
document.querySelector('#emails-view').style.display = 'none';
document.getElementById('mail-template').style.display = 'none';
setTimeout(() =>
{
document.getElementById('mail-template').style.display = 'block';
},
300
);
document.querySelector('#compose-view').style.display = 'none';
fetch(`emails/${mail_id}`).then(
response => response.json())
.then(result =>
{
let mail = `<div><strong>From</strong>: ${result['sender']}</div>`;
mail += `<div><strong>To</strong>: ${result['recipients']}</div>`;
mail += `<div><strong>Subject</strong>: ${result['subject']}</div>`;
mail += `<div><strong>Timestamp</strong>: ${result['timestamp']}</div>`;
if (window.mail_title != 'Sent'){
mail += '<p><input value="Reply" type="submit" class="btn btn-sm btn-outline-primary" id="reply"/>'
mail += `<input id="${mail_id}" value="Unread" type="submit" class="btn btn-sm btn-outline-primary read" style="float: right;"/>`
mail += `<input value="" id="${mail_id}" type="submit" class="btn btn-sm btn-outline-primary archive" style="float: right; margin-right: 5px;"/></p>`
}
mail += '<hr>'
mail += `${result['body']}`
mail_template.innerHTML = mail;
declare_results(result);
let arch = document.querySelector('.archive');
if (window.mail_title != 'Sent'){
if (result['archived'] == true) {
arch.value = 'Unarchive';
}else if (arch.value == ""){
arch.value = "Archive";
}
}
})
read_unread(mail_id, 'read');
}
// Declare the returned value from fetch email above
function declare_results(value){
window.results = value;
}
// Event to track if the 'reply' button was clicked
mail_template.addEventListener('click', e => {
if (e.target.id === 'reply'){
window.tops = `${window.results['sender']}`;
window.subject = `${window.results['subject']}`;
window.body = `${window.results['body']}`;
window.date = `${window.results['timestamp']}`;
/*
Here is where I call the function with arguments
which act as expected.
*/
compose_email(window.tops, window.subject, window.body, window.date);
}
else if (e.target.value == 'Unread') {
read_unread(parseInt(e.target.id), 'unread')
e.target.value = 'Read';
}
else if (e.target.value == 'Read') {
read_unread(parseInt(e.target.id), 'read')
e.target.value = 'Unread';
}
else if (e.target.value == 'Unarchive') {
archive(parseInt(e.target.id), 'unarchive')
e.target.value = 'Archive';
}
else if (e.target.value == 'Archive') {
archive(parseInt(e.target.id), 'archive')
e.target.value = 'Unarchive';
}
});
document.addEventListener('DOMContentLoaded', function() {
// Use buttons to toggle between views
document.querySelector('#inbox').addEventListener('click', () => load_mailbox('inbox'));
document.querySelector('#sent').addEventListener('click', () => load_mailbox('sent'));
document.querySelector('#archived').addEventListener('click', () => load_mailbox('archive'));
/*
Here is where I call it without arguments; unfortunately,
all the parameters get mouseevent as values in the compose_mail
function.
*/
document.querySelector('#compose').addEventListener('click', compose_email);
// By default, load the inbox
load_mailbox('inbox');
});
/*
This is the function that receives the argument. The argument
were default argument as shown in the previous
code (there, one argument [receiver]). Because the parameters get
these mouse event as argument, I used If-else statement to
update the values if none were given (the expected parameters are not
string) before using them.
*/
function compose_email(receiver, subject, body, date) {
// Show compose view and hide other views
document.querySelector('#emails-view').style.display = 'none';
document.getElementById('mail-template').style.display = 'none';
document.querySelector('#compose-view').style.display = 'block';
// Clear out composition fields
if (typeof receiver != 'string') {
document.querySelector('#compose-recipients').value = '';
document.querySelector('#compose-subject').value = '';
document.querySelector('#compose-body').value = '';
} else {
document.querySelector('#compose-recipients').value = receiver;
document.querySelector('#compose-subject').value = 'Re: ' + subject;
document.querySelector('#compose-body').value = `On ${date} ${receiver} wrote: ` + body;
}
}
document.querySelector('#compose-form').addEventListener('submit', (e) => {
document.querySelector('#compose-recipients').value;
let recipients = document.querySelector('#compose-recipients').value;
let subject = document.querySelector('#compose-subject').value;
let body = document.querySelector('#compose-body').value;
e.preventDefault();
// Send mail to the backend
fetch('/emails', {
method: 'POST',
body: JSON.stringify({
recipients: recipients,
subject: subject,
body: body
})
}).then(response => response.json())
.then(result => {console.log(result)})
load_mailbox('sent')
});
function load_mailbox(mailbox) {
// Show the mailbox and hide other views
document.querySelector('#emails-view').style.display = 'block';
document.getElementById('mail-template').style.display = 'none';
document.querySelector('#compose-view').style.display = 'none';
// Show the mailbox name and get its textContent
document.querySelector('#emails-view').innerHTML = `<h3 class="mail-title">${mailbox.charAt(0).toUpperCase() + mailbox.slice(1)}</h3>`;
window.mail_title = document.querySelector('.mail-title').textContent
// Retrieve user's sent emails
fetch(`/emails/${mailbox}`).then(response => response.json())
.then(emails => {
emails.forEach( email => {
let div_top = document.createElement('div')
let div = document.createElement('div')
div.className = 'mail-boxs';
if (window.mail_title != 'Sent'){
if (email['read'] === true){
div.style.backgroundColor = '#DCDCDC';
}else {
div.style.backgroundColor = 'white';
}
}
div.id = `${email['id']}`;
div.style.border = 'solid 1px gray';
div.style.padding = '8px';
let email_sub = document.createElement('span')
email_sub.className = 'mail-boxs';
email_sub.innerHTML = `<strong class="mail-boxs">${email['sender']}</strong> ${email['subject']}`
let time_stamp = document.createElement('span')
time_stamp.innerHTML = `${email['timestamp']}`
time_stamp.className = 'mail-boxs';
time_stamp.style.float = 'right';
div_top.className = 'move';
div.appendChild(email_sub);
div.appendChild(time_stamp);
div_top.append(div);
emails_view.appendChild(div_top);
});
});
}
// Event to get the div element (message) that
// was clicked
emails_view.addEventListener('click', e => {
if (e.target.className == 'mail-boxs'){
let mail_id = check_parent(e.target);
open_email(parseInt(mail_id))
}
})
The command emails_view.addEventListener('click', e => { is inside a foreach loop.
Based on the next lines, you inspect if the class name is 'mail-boxs' which was applied to the div element, should the EventListener be added to div instead? Like this:
div.addEventListener('click', e => {
The mail_template.addEventListener('click', e => { seems to add an EventListener each time you open an email since you are replacing only the InnerHtml.
In this case i suggest you remove the previous click EventListener, before adding a new one but it seems i little tricky with anonymous functions:
Removing an anonymous event listener
How to remove all listeners in an element?

change list item to be an input text

I am working on "to do list" using Vanilla JavaScript and stuck with edit button.
This is what I have done so far.
// console.log("Hello from inside");
const input = document.getElementById("inputText");
const display = document.getElementById("display");
const displayDone = document.getElementById("display-done");
const button = document.getElementById("addButton");
const deleteAll = document.getElementById("delete-icon");
// const ul = document.getElementById("display");
// Create and Remove list item
function createItem() {
let listItem = document.createElement("li");
listItem.className = "create";
listItem.appendChild(document.createTextNode(input.value));
display.appendChild(listItem);
input.value = "";
let editButton = document.createElement("button");
editButton.innerText = "edit";
editButton.className = "edit";
editButton.addEventListener("click", (e) => {
//console.log(e.currentTarget.parentNode);
// 1. When edit button is clicked, change list item to be an input text. <input type="text" value="my to do task"/>
// 2. Change edit button to be something like "complete" button
editButton.innerText = "complete";
// 3. When complete button is clicked, change the input item to be a list item again with the updated text.
editButton.addEventListener("click", (e) => {
editButton.innerText = "edit";
});
});
let removeTask = document.createElement("input");
removeTask.setAttribute("type", "button");
removeTask.setAttribute("value", "X");
removeTask.setAttribute("id", "removeButton");
removeTask.addEventListener("click", (e) => {
listItem.parentNode.removeChild(listItem);
});
listItem.appendChild(editButton);
listItem.appendChild(removeTask);
listItem.addEventListener("dblclick", (e) => {
displayDone.appendChild(listItem);
listItem.style.backgroundColor = "#54e346";
listItem.style.textDecoration = "line-through";
editButton.style.display = "none";
});
}
//Make sure user fill the input
function addAfterClick() {
if (input.value.length > 0) {
createItem();
} else {
alert("Write something mate!");
}
}
//List item can be added with "Enter" as well
function addWithEnter(event) {
if (event.keyCode === 13) {
addAfterClick();
}
}
// Remove all with user approve
function removeAll(event) {
let answer = prompt(`Type "Delete All" If you are that sure!`);
if (answer === "Delete All") {
display.style.display = "none";
location.reload();
} else {
alert("Try One More Time");
}
}
//Event Listeners
button.addEventListener("click", addAfterClick);
input.addEventListener("keyup", addWithEnter);
deleteAll.addEventListener("click", removeAll);
<div class="main-container">
<h2 class="title">to do list <i class="fas fa-pencil-alt"></i></h2>
<div class="content">
<input
id="inputText"
type="text"
placeholder="Add to your list..."
maxlength="20"
/>
<i id="addButton" class="fas fa-plus"></i>
<div class="remove-container">
<i id="delete-icon" class="far fa-trash-alt"></i>
<p id="remove-text">Delete All</p>
</div>
<ul id="display"></ul>
</div>
<div class="completed">
<h2 class="title-done">
completed tasks <i class="far fa-check-circle"></i>
</h2>
<ul id="display-done"></ul>
</div>
</div>
What I want to do it;
When edit button is clicked, change list item to be an input text.
Change edit button to be something like "complete" button
When complete button is clicked, change the input item to be a list item again with the updated text.
I have tried ChildNode.replaceWith() but did not work. Am I on the right path? and also what direction(s) I should follow. Thanks in advance!
My approach is to add an input field everytime you create an item:
at initial insertion hide the input field
on click on the edit button show the input and change the textNode
after you editing
console.log("Hello from inside");
const input = document.getElementById("inputText");
const display = document.getElementById("display");
const displayDone = document.getElementById("display-done");
const button = document.getElementById("addButton");
const deleteAll = document.getElementById("delete-icon");
// const ul = document.getElementById("display");
// create edit input and hide the input at first
const editInput=document.createElement("input");
editInput.setAttribute("hidden",true);
// eventhandler for editInput
function editItem(e,item){
// remove the current text node
e.target.parentNode.removeChild(e.target.parentNode.childNodes[0])
// insert at the beginning of the childNodes
e.target.parentNode.prepend( e.target.value)
e.target.setAttribute("hidden",true)
}
// Create and Remove list item
function createItem() {
let listItem = document.createElement("li");
listItem.className = "create";
listItem.appendChild(document.createTextNode(input.value));
display.appendChild(listItem);
input.value = "";
let editButton = document.createElement("button");
editButton.innerText = "edit";
editButton.className = "edit";
editInput.value=""
editButton.addEventListener("click", (e) => {
// 1. When edit button is clicked, change list item to be an input text. <input type="text" value="my to do task"/>
// 2. Change edit button to be something like "complete" button
// appended edit input
listItem.append(editInput)
if(e.target.innerText === "edit"){
editInput.removeAttribute("hidden",false)
e.target.innerText = "complete";
}else{
e.target.innerText = "edit";
}
// add the event handler for the Editinput
editInput.addEventListener("change",(e)=>editItem(e,listItem));
editInput.value=""
// 3. When complete button is clicked, change the input item to be a list item again with the updated text.
});
let removeTask = document.createElement("input");
removeTask.setAttribute("type", "button");
removeTask.setAttribute("value", "X");
removeTask.setAttribute("id", "removeButton");
removeTask.addEventListener("click", (e) => {
listItem.parentNode.removeChild(listItem);
});
listItem.appendChild(editButton);
listItem.appendChild(removeTask);
listItem.addEventListener("dblclick", (e) => {
displayDone.appendChild(listItem);
listItem.style.backgroundColor = "#54e346";
listItem.style.textDecoration = "line-through";
editButton.style.display = "none";
});
}
//Make sure user fill the input
function addAfterClick() {
if (input.value.length > 0) {
createItem();
} else {
alert("Write something mate!");
}
}
//List item can be added with "Enter" as well
function addWithEnter(event) {
if (event.keyCode === 13) {
addAfterClick();
}
}
// Remove all with user approve
function removeAll(event) {
let answer = prompt(`Type "Delete All" If you are that sure!`);
if (answer === "Delete All") {
display.style.display = "none";
location.reload();
} else {
alert("Try One More Time");
}
}
//Event Listeners
button.addEventListener("click", addAfterClick);
input.addEventListener("keyup", addWithEnter);
deleteAll.addEventListener("click", removeAll);
<div class="main-container">
<h2 class="title">to do list <i class="fas fa-pencil-alt"></i></h2>
<div class="content">
<input
id="inputText"
type="text"
placeholder="Add to your list..."
maxlength="20"
/>
<i id="addButton" class="fas fa-plus"></i>
<div class="remove-container">
<i id="delete-icon" class="far fa-trash-alt"></i>
<p id="remove-text">Delete All</p>
</div>
<ul id="display"></ul>
</div>
<div class="completed">
<h2 class="title-done">
completed tasks <i class="far fa-check-circle"></i>
</h2>
<ul id="display-done"></ul>
</div>
</div>
just a suggestion. you can use an input element for each item.
const input = document.getElementById("inputText");
const display = document.getElementById("display");
const displayDone = document.getElementById("display-done");
const button = document.getElementById("addButton");
const deleteAll = document.getElementById("delete-icon");
const createEditBtn = () => {
const editButton = document.createElement("button");
editButton.innerText = "edit";
editButton.className = "edit";
editButton.addEventListener("click", (e) => {
const btn = e.target;
const input = btn.parentElement.querySelector("input");
input.focus();
const previousVal = input.value;
input.addEventListener("keyup", (evt) => {
if (evt.keyCode === 13) {
if (input.value.trim() === "") {
input.value = previousVal;
}
input.disabled = true;
btn.disabled = false;
}
});
input.addEventListener("blur", () => {
input.disabled = true;
btn.disabled = false;
});
input.disabled = false;
btn.disabled = true;
});
return editButton;
}
const createRemoveBtn = () => {
const removeBtn = document.createElement("input");
removeBtn.setAttribute("type", "button");
removeBtn.setAttribute("value", "X");
removeBtn.addEventListener("click", (e) => {
e.target.parentElement.remove();
});
return removeBtn;
}
const createItem = () => {
const listItem = document.createElement("li");
listItem.innerHTML = `<input value="${input.value}" disabled=true>`;
display.appendChild(listItem);
input.value = "";
const editBtn = createEditBtn();
listItem.appendChild(editBtn);
listItem.appendChild(createRemoveBtn());
listItem.addEventListener("dblclick", (e) => {
displayDone.appendChild(listItem);
listItem.style.backgroundColor = "#54e346";
listItem.style.textDecoration = "line-through";
editBtn.style.display = "none";
});
}
const addAfterClick = () => {
if (input.value.length > 0) {
createItem();
} else {
alert("Write something mate!");
}
}
//List item can be added with "Enter" as well
const addWithEnter = (event) => {
if (event.keyCode === 13) {
addAfterClick();
}
}
// Remove all with user approve
const removeAll = (event) => {
const answer = prompt(`Type "Delete All" If you are that sure!`);
if (answer === "Delete All") {
display.innerHTML = "";
displayDone.innerHTML = "";
} else {
alert("Try One More Time");
}
}
//Event Listeners
button.addEventListener("click", addAfterClick);
input.addEventListener("keyup", addWithEnter);
deleteAll.addEventListener("click", removeAll);
<div class="main-container">
<h2 class="title">to do list <i class="fas fa-pencil-alt"></i></h2>
<div class="content">
<input id="inputText" type="text" placeholder="Add to your list..." maxlength="20" />
<i id="addButton" class="fas fa-plus">add Icon</i>
<div class="remove-container">
<i id="delete-icon" class="far fa-trash-alt">delete icon</i>
<p id="remove-text">Delete All</p>
</div>
<ul id="display"></ul>
</div>
<div class="completed">
<h2 class="title-done">
completed tasks <i class="far fa-check-circle"></i>
</h2>
<ul id="display-done"></ul>
</div>
</div>

Categories