简体   繁体   中英

jQuery AJAX calls getting fired multiple times within click handler

I'm currently developing a PHP web app, and I'm making use of the datatables jQuery plugin, and jQuery AJAX calls to create a dynamic table that can have elements edited, deleted and added. This (seems to be) working fine, however, I've observed that within some of my click handlers, AJAX calls are being fired multiple times - and I'm not quite sure why.

Here is the page at the moment so you have an idea of where I'm coming from:

显示表格的屏幕截图

As you can see, I have basic data on users, and actions to the left. Each row in the table looks like this in the HTML:

<tr>
    <td><?= $userAccount['firstName'] ?></td>
    <td><?= $userAccount['lastName'] ?></td>
    <td><?= $userAccount['email'] ?></td>
    <td><?= $userAccount['jobTitle'] ?></td>
    <td class="text-right enrolr-datatable-actions-min-width">
        <i data-userId="<?= $userAccount['id'] ?>" class="fas fa-user-edit enrolr-standard-icon mr-2 event-user-edit"></i>
        <i data-userId="<?= $userAccount['id'] ?>" class="fas fa-user-times enrolr-danger-icon mr-2 event-user-delete-staff"></i>
    </td>
</tr>

Since (after the initial page load/render) these table rows are being removed/added dynamically, I decided to therefore listen for events at the document level for clicks on the .event-user-delete-staff class, like so:

$(document).on('click', '.event-user-delete-staff', function () {
    // Storing some details for later use by handlers
    const userEmail = $(this).closest('tr').find('td').eq(2).html();
    const $button = $(this);

    // Shows confirm dialog, only performs deletion when "yes" is clicked, which causes the third function parameter to run
    confirmDialog(`Are you sure you want to delete ${userEmail}? This action cannot be undone.`, 'Confirm Deletion', function () {
        const $parentToRemove = $button.closest('tr');

        // Make post to remove user.
        $.ajax({
            type: 'POST',
            url: '../php/account/_deleteUser.php',
            data: {
                id: $button.attr('data-userId')
            },
            dataType: 'json',
            success: function (response) {
                // Handle response.
                if (response.success == true) {
                    displaySuccessToast(response.message);
                    // Fade out row then update datatable.
                    $parentToRemove.fadeOut(500, () => {
                        staffTable.row($parentToRemove).remove().draw();
                    });
                } else {
                    displayErrorToastStandard(response.message);
                }
            },
            error: function () {
                // Show error on failure.
                displayErrorToastStandard('Something went wrong while handling this request');
            }
        });
    });
});

Now, the reason I noticed the AJAX calls are being made multiple times is because I noticed multiple toast messages are being displayed for each delete event (The displaySuccessToast method, all this does is clone a toast template and display it). The following basically happens:

  1. First deletion from table - works fine, row is removed and success message displays.
  2. Second deletion from table - This is where things start to get funky. Correct row is deleted from table, however, two toasts show and two AJAX calls are made.
  3. Third deletion from table - Trend continues, now 3 toasts get displayed and 3 AJAX calls are made.

Having added some console logs and stepping through the code, I can tell this isn't because the $(document).on('click', '.event-user-delete-staff', function () event listener is firing multiple times. It only fires once per deletion, as it should.

I think the suspect is the confirmDialog() method, as I've observed via some console logs, it is in here that it is firing multiple times.

This method looks like this:

window.confirmDialog = function (message, title, yesCallback) {
    // Sets some details in the confirm dialog
    $('#confirmMessage').html(message);
    $('#confirmTitle').html(title);

    // Shows the modal
    $('#confirmModal').modal('show');

    // On the "yes" button within the dialog being clicked, hides modal and calls the yesCallback function.
    $('#confirmBtnYes').on('click', function () {
        $('#confirmModal').modal('hide');
        yesCallback();
    });

    // On "no" just hides the modal.
    $('#confirmBtnNo').on('click', function () {
        $('#confirmModal').modal('hide');
    });
}

However, I suspect it's not really the fault of this method; but is some kind of misunderstanding of mine as to how function callbacks like this work, and using variables from the parent scope within them?

As I've said; I don't think this is because the event listener itself is firing multiple times, on the second deletion (and beyond...), the event still only logs being called once, it is within the confirmDialog function callback method where it logs twice.

Can someone please advise what I'm doing wrong here, and what could be causing this behaviour?

Edit - For posterity, and anyone else who stumbles across this and wants to know what I changed...

The problem in this case, is that each time the confirmDialog method was being called, it would add another listener to the yes button. This means, when the yes button in that dialog was clicked, it would fire the most recent event... and all the other ones attached before. I simply changed the confirm button yes dialog to look like so:

$('#confirmBtnYes').off().on('click', function () {
    $('#confirmModal').modal('hide');
    yesCallback();
});

Using off() first ensures no other event listeners are lingering around on that yes button. Probably not the best or cleanest solution, but hey, it works.

A thank you to Nick with his answer below , who pointed out where I was going wrong

You're adding a new event handler to the Yes button in the confirm dialog every time you call confirmDialog :

$('#confirmBtnYes').on('click', function () {
    $('#confirmModal').modal('hide');
    yesCallback();
});

This means that the first time you click the yes button, yesCallback will get called once, the second time yesCallback will get called twice, etc. etc.

You're also doing the same for the No button, but since that just hides the modal it doesn't really affect the operation of the page.

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