简体   繁体   中英

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?

Comment if this helped, i would like to know.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM