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))
}
})