How to prevents events from firing multiple time - javascript

For some reason I have to call the same function twice because of that my event are firing twice. How can I prevent this?
function renderSeats(movieImg, title, release_date, dummyPrice = '3000') {
// selected items
//this is only a part of the code .
contianer.addEventListener('click', function (e) {
if (
e.target.classList.contains('seat') &&
!e.target.classList.contains('unavailable')
) {
e.target.classList.toggle('selected');
payoutBtn.classList.add('active');
console.log('yes');
updateCountTotal(); //function call
}
contianer.setAttribute('listener', 'true');
});
const ticketOverlayClose = document.querySelector('.ticket__close-svg');
// overlay deactive
ticketOverlayClose.addEventListener('click', function (e) {
overlayseats.classList.add('hidden');
payoutBtn.classList.add('active');
ticketContainer.classList.remove('active');
});
}
Every time I am calling the function from another function because I have to update the dom, suppose 3 times the event then also fire 3 times
I also attached an image. In the console, you see after I clicking back (it is a single-page app) and again load by page calling our rendered seat function, and this time when I click on the seats the even is firing twice.
[gif][1]:https://i.stack.imgur.com/Ik1CG.gif

Related

JavaScript event in function triggered multiple times

I am having a hard time understanding some aspects of JavaScript functions and event handlers.
In the application I'm working on, the user owns a collection of books. Each time he connects, a list of his books is displayed and he has a button to delete a book if he wants to. First, I wrote the following function:
function deleteBook(book_to_delete) {
console.log('Called');
// Other actions to perform to delete the book
}
$(document).on('click', '#user-books-list button.delete', function () {
var self = $(this);
var book_to_delete = self.attr('id').replace('delete-', '');
console.log('Book to delete: ' + book_to_delete);
// If the user wants to delete a book :
// Then display a warning on a modal
$('#confirm-book-deletion').removeClass('hidden');
// If the user clicks 'no' then hide the warning and do nothing else
$('#book-deletion-no').on('click', function () {
// Hiding the modal
$('#confirm-book-deletion').addClass('hidden');
});
// It the user clicks yes, then delete the book
$('#book-deletion-yes').on('click', function () {
// Display the book's id and call deletebook()
console.log('Trigger for ' + book_to_delete);
deleteBook(book_to_delete);
// Hiding the modal
$('#confirm-book-deletion').addClass('hidden');
});
// Make sure we exit this function
return;
});
The problem is that my trigger $('#book-deletion-yes') get fired multiple times.
If I do the following:
Book 1 --> Delete --> Cancel (triggers $('#book-deletion-no'), nothing happens)
Book 2 --> Delete --> Cancel
Book 3 --> Delete --> Confirm
Then instead of only deleting book 3, it deletes books 1, 2, and 3.
The "Book 3" step returns the following :
Book to delete: 3
Trigger for 1
Called
Trigger for 2
Called
Trigger for 3
Called
I changed my code to the following, moving $('#book-deletion-no') and $('#book-deletion-yes') out of the main function, and now it works:
function deleteBook(book_to_delete) {
// Make sure there is indeed a book to delete
if (book_to_delete !== undefined) {
console.log('Called');
// Other actions to perform to delete the book
} else {
console.error('Book id undefined');
}
}
var book_to_delete = undefined;
$(document).on('click', '#user-books-list button.delete', function () {
var self = $(this);
book_to_delete = self.attr('id').replace('delete-', '');
// Then display a warning
$('#confirm-book-deletion').removeClass('hidden');
});
// If the user clicks 'no' then hide the warning and do nothing else
$('#book-deletion-no').on('click', function () {
$('#confirm-book-deletion').addClass('hidden');
// Reset book_to_delete value
book_to_delete = undefined;
});
// It the user clicks yes, then delete the book
$('#book-deletion-yes').on('click', function () {
$('#confirm-book-deletion').addClass('hidden');
// Delete the book
console.log('Trigger for ' + book_to_delete);
deleteBook(book_to_delete);
});
Can someone help me understand why my first attempt did not work, and the main differences between the two? I really don't get why the first code does this weird "loop" through all the former book_to_delete values as this is not even an array.
Thanks
I am reposting here #Teemu's answer to close this topic.
In the first snippet you're adding new click listeners every time the
document is clicked. jQuery on is based on the native
addEventListener, which is capable to attach multiple listeners of the
same type to the elements, the newly-attached event doesn't override
the existing events.
My problem was that I didn't understand how jQuery on worked and was attaching one event on each click, for each book, even if I chose to "cancel" the deletion. Once I finally chose to delete a book, it triggered all of the events... and thus deleted all the books I already clicked on at least once.
jQuery on reference :
https://api.jquery.com/on/
The .on() method attaches event handlers to the currently selected set of elements in the jQuery object.
This method keeps attaching events to the clicked element. It does not replace the event on each click. To detach an event, use the off method.

Query select elements everytime partial is loaded by Vanilla Javascript in Rails

I have a partial inside a partial which has radio buttons or check boxes. What I want to do is to get the radio button or checkboxes which are clicked. Now initially when the page is loaded I am able to get the buttons but when I go to the next partial I am not able to get the new buttons. How can I get the buttons of the new partial everytime some button is clicked in a separate js. If I use onclick function inline with radio button or checkbox then the function is called correctly but I want to get the present displayed elements in a separate js file.
I tried to use window.addEventListener('change'). If I use this then the function is not called on first click but in the subsequent clicks it calls that many number of times i.e., on second click the function is called once, on third click the function is called twice and so on.
// window.addEventListener('change', () => {
window.addEventListener('DOMContentLoaded', () => {
if (document.querySelectorAll('[name="question[answer_id]"]').length !== 0) {
document.querySelectorAll('[name="question[answer_id]"]').forEach((questionAnswerButton) => {
questionAnswerButton.addEventListener('click', (event) => {
console.log(event);
fetchCall(event.target.value);
});
});
}
});
radio_button_partial.html.erb
<%= radio_button_tag 'question[answer_id]',
answer.id,
(user_answer == answer.id),
{
class: 'answer_opt',
// onclick: fetchCall("<%= answer.id %>")
} %>
Here if I uncomment the onclick function then I get the desired functionality. But what should I change in this that I get the present displayed radio buttons from the separate js file?
Instead of attaching a listener directly to the elements you want to use event bubbling:
document.addEventListener('click', (event) => {
if (event.target.matches('[name="question[answer_id]"]')) {
console.log(event);
fetchCall(event.target.value);
}
});
When an event is fired it up bubbles up the DOM until a handler is found. Unlike attaching event handlers directly to the elements this is idempotent and the handler will work for elements dynamically inserted into the page. Its also compatible with turbolinks.
This code should not be placed in a script tag or .js.erb abomination as it will add a handler every time the code is executed. Put it in the assets pipeline.
If fetchCall does an ajax call you will want to use a debouncing technique such as disabling the input and re-enabling it when the promise is resolved.
document.addEventListener('click', (event) => {
if (event.target.matches('[name="question[answer_id]"]')) {
console.log(event);
// Todo refactor fetchCall so that it returns a promise
let promise = fetchCall(event.target.value);
event.target.disabled = true;
promise.then((successMessage) => {
event.target.disabled = false;
});
}
});

Click on button first time show alert, click second time execute code

I want to show users, when they click on a button for the first time, an alert when in a date field a value is chosen which lies before the current date. When they insist to this choice for good reasons, I want them to give a second chance to click on the same button, and then the value has to be submitted.
The click event is defined in a function:
$("#edit_date_btn").click(function(){
// do something and save
}
In an other function the comparing is handled. The basic code is:
function edit_date_compare() {
....
if(usersDate < today)
{ //show alert
return false; // needed for the first click so the input is not submitted
}
I've tried several options e.g. with a count on the click function (on a second click 'return true;' instead of 'return false;') but it seems difficult to handle this situation. Any ideas how to make this successful? It could be with Javascript or jQuery.
You have the right idea with the count on the click function. I suspect you might have difficulty implementing it. You need a separate variable that tracks the number of clicks. Here's a working fiddle: http://jsfiddle.net/zwmquxaL/
$(document).ready(function () {
var clickCount = 0;
$("#btnSave").click(function () {
if (clickCount > 0) {
AlertSave();
} else {
AlertFirst();
clickCount++;
}
});
});
function AlertSave() {
alert("Some code has been executed!");
}
function AlertFirst() {
alert("Are you sure you want to perform this operation?");
}

Detecting a button that is loaded asynchronously

On a web page I am looking at, there is a lot of data being loaded asynchronously.
Included with that data is a button called "View More". If you either click on the button or simply scroll right to the bottom, more data will be loaded via a "GET" call function.
When I try using this (in FireBug console):
document.getElementById('#button9').click();
or
document.getElementById('button9').click();
I get this result:
TypeError: document.getElementById(...) is null
document.getElementById('#button9').click();
From what I have read about this TypeError, the Id cannot be detected, which I assume is because of the data being asynchronous. Is there a way to detect the element (it doesn't show up on the "page source" but it is there when I click on "Inspect Element with FireBug")?
I would like to detect the element and then make a call to a click event to 'simulate a click' (meaning I would like a click to take place without clicking on the button or scrolling right down) with the output being displayed in the browser (per usual).
If you just want to attach the event, then the bottom will work.
If the problem is that it is null, and you don't know when it will show up, your best bet is to create a loop that runs until it exists.
var obj = document.getElementById('button9');
obj.addEventListener('click', function() {
alert(' you clicked on ' + this);
}, false);
in case its that second instance, and you want to wait until the object events, then use:
checkExists();
function checkExists() {
var obj = document.getElementById('button9');
if (obj != null) {
obj.addEventListener('click', function() {
alert(' you clicked on ' + this);
}, false);
alert('added event');
}
else {
setTimeout(checkExists, 1000);
}
}
This will work, no matter even if you don't know when the object is getting created and added to the DOM
//chrome / ff /IE
$("#button9").bind("DOMSubtreeModified", function() {
// Add click function here
});
//IE 8 and lower
$("#button9").bind("propertychange", function() {
//Add click function here
});
2nd Option
*OR*
Not sure if it supposts IE8 though but it definitely supports all others
document.addEventListener("DOMSubtreeModified", function (e) {
if ($(e.target.id) == "#button9") {
// Do stuff
}
}, false)
Explanation: It checks if the DOM has found an element with the specific ID, if so, execute the click function.

Jquery automatic loading gif and button disable on submit click

Is it possible to automate the showing/hiding of a ajax loading gif, and the disabling/enabling of the submit button at the same time? (when the submit button is a styled < a > not a input type=submit)
Currently when submitting I do this:
$("#save_button_id").click(function () {
if ($('#save_button_id').hasClass('ui-state-disabled')) return false;
Save();
});
function Save() {
StartAjax($("#save_button_id"));
$.ajax({
success: function (data) {
EndAjax($("#save_button_id"));
// etc...
},
error: function (xhr, status, error) {
EndAjax($("#save_button_id"));
// etc ...
}
});
}
function StartAjax(button) {
DisableButtonClick(button);
$("#ajaxLoad").show();
}
function EndAjax(button) {
EnableButtonClick(button);
$("#ajaxLoad").hide();
}
I've seen a few places talk about how to use .ajaxStart() to automatically show the loading gif, but is it possible to also find a reference to the button (a styled < a > tag) that was clicked, and automatically disable/enable that as well?
The point of this is to not have to manually type in Start/EndAjax every time, and to make sure the app is consistent everywhere.
EDIT
None of the answers so far offer automation - any solution (like my current one above) where you have to manually type start/end before and after every single $.ajax() causes a maintenance problem: Its easy to forget to place the start/end next to some $.ajax() calls, and if you want to change how it works later you need to go through every single one to make the change.
EDIT 2 - to clarify point re the .delegate() suggestion
You said "You can attach your event handler to any element" - but I want to attach it to every button element (DRY!): so I've modified the 1st part of your suggestion to be:
$('div#buttons a.ui-icon').each(function(index) {
$(this).ajaxStart(function() {
$("#ajaxLoad").show();
});
});
This solves the first problem, which is how to show the loading .gif for any button, without having to repetitively type "$("#ajaxLoad").show()" everywhere there is an $.ajax() call.
The next part is how to disable any button when it is clicked (again with no repetitive code) - you suggested .delegate(). But in your example every button click will call the Save() method. (I changed the selector to match my html)
$('div#buttons a.ui-icon').delegate('button:not(.ui-state-disabled)', 'click', function() {
$(this).toggleClass('ui-state-disabled');
Save(); // this line needs replacing with a call to $(this).something
});
The problem with this is that $(this) is not always the save button (the selector returns ALL the buttons), it might be the delete button or the cancel button or etc. So calling $(this).toggleClass is fine, but calling Save() means you are calling the wrong function. All these buttons already have a .click method:
$("#SaveButton").click(function () {
Save();
});
$("#DeleteButton").click(function () {
Delete();
});
So this is the original click function that needs to be called where it says $(this).something above. At this point it should be calling the original click - or perhaps it is more correct to say it should then bubble up to the original .click. The .delegate must be more generic, and the original .click will provide the specific implementation.
It actually turned out to be very straightforward: I've included this in my helper.js file that is run for every page:
$(document).ready(function () {
$('div#buttons a.fm-button').each(function (index) {
$(this).ajaxStart(function () {
$("#ajaxLoad").show();
DisableButtonClick($(this));
});
$(this).ajaxStop(function () {
$("#ajaxLoad").hide();
EnableButtonClick($(this));
});
});
});
Now whenever any of the ajax-y buttons are clicked on any page, the buttons are disabled and the ajax loading gif is shown. When the ajax call ends they are returned to their normal state. And all done with out repetitively typing code every time .ajax() is called.
You can attach your event handler to any element; so for your save button:
$(this).ajaxStart(function() {
$("#ajaxLoad").show();
});
Now, you can listen for the click event for each button on your page and call the ajax function from that element:
$('div').delegate('button:not(.ui-state-disabled)', 'click', function() {
$(this).toggleClass('ui-state-disabled');
Save();
});
The $.delegate() function listens to click events for each element that does not have the .ui-state-disabled class. When an event is fired, it will toggle the class of that button to disabled, call your Save function and the $.ajaxStart() event will fire. That event will show your animated ajax gif.
I have assumed that your buttons are all contained in a div for this example. You many want to modify the code so that $('div').delegate... actually specifies the containing element of your buttons. I am also assuming you're using jQuery 1.4.2.
You should be able to use the $.ajaxStop() function to revert everything back again.
$("#save_button_id").click(function () {
if ($('#save_button_id').hasClass('ui-state-disabled')) {
return false;
} else {
// add disabled class
$('#save_button_id').addClass('ui-state-disabled');
// optionally disable the button if it's an input button
$('#save_button_id').attr('disabled','disabled');
// show ajax loader
$("#ajaxLoad").show();
$.ajax({
url: 'ajax/stuff.html',
success: function(data) {
// do stuff
// re-enable button
$('#save_button_id').removeClass('ui-state-disabled');
// optionally re-enable the button if it's an input button
$('#save_button_id').attr('disabled','');
// hide ajax loader
$("#ajaxLoad").hide();
},
error: function (xhr, status, error) {
// do stuff
// re-enable button
$('#save_button_id').removeClass('ui-state-disabled');
// optionally re-enable the button if it's an input button
$('#save_button_id').attr('disabled','');
// hide ajax loader
$("#ajaxLoad").hide();
});
}
// prevent default action, if any
return false;
});
Edit: and to put those in a function:
function processing(status) {
if (status == 1) {
// add disabled class
$('#save_button_id').addClass('ui-state-disabled');
// optionally disable the button if it's an input button
$('#save_button_id').attr('disabled','disabled');
// show ajax loader
$("#ajaxLoad").show();
} else {
// re-enable button
$('#save_button_id').removeClass('ui-state-disabled');
// optionally re-enable the button if it's an input button
$('#save_button_id').attr('disabled','');
// hide ajax loader
$("#ajaxLoad").hide();
}
}
And then call on that function:
$("#save_button_id").click(function () {
if ($('#save_button_id').hasClass('ui-state-disabled')) {
return false;
} else {
processing(1);
$.ajax({
url: 'ajax/stuff.html',
success: function(data) {
// do stuff
processing(0);
},
error: function (xhr, status, error) {
// do stuff
processing(0);
});
}
// prevent default action, if any
return false;
});

Categories

Resources