1

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:

screenshot showing table

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 👍

3
  • Is this an old version of jQuery? Upgrade and use .one() That MIGHT solve the problem. If you post generated HTML, it would help. Read this stackoverflow.com/questions/26925611/… and this stackoverflow.com/questions/26475445/… Commented Dec 18, 2020 at 1:54
  • 1
    Hey there, thank you for the quick comments, I have watched the event listeners tab - and as far as I can tell, there aren't any new event listeners being created each time. I can verify this via console logs, as it only logs once from within the direct scope of the event handler. This is not an old version of jQuery, I will try out using the one() method and if that helps. Commented Dec 18, 2020 at 1:59
  • @JakeH I was about to post something similar to the answer that Nick provides however, Ill recomend you to load the table data with another Ajax call, cause you could reach a stack point somewhere down the line Commented Dec 19, 2020 at 5:47

1 Answer 1

2

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Damn, this is a facepalm moment. How could I go about doing this so that I don't end up adding multiple event listeners to the "yes" button? Just use one instead of on?
Just add those event handler assignments into the code that creates the confirm dialog modal (or $(document).ready code if it's in the HTML for the page)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.