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.
Related
I am calling JQuery global function on button click
$.fn.editFaculty = function() {
var id = $(this).data('id');
var name = $(this).data('name');
$('#editFacultyName').val(name);
console.log(id); //this works fine
$('#update_btn').click(function (e){
e.preventDefault()
console.log('btn ' + id) //this keeps on adding and sends multiple requests...
})
};
on button click
onclick="$(this).editFaculty();"
On button click this function gets called along with a popup and shows value in popup, then when I click update_button button then I am doing updating value (through ajax but not included in question).
Issue is when I click a button with id=1 (id is not id attribute of html it means the button which corosponds to item which has id 1),
first console.log(id); shows 1 and second console.log('btn' + id) shows btn 1. When I again click on other button with id 2
first console.log(id); shows 2 and second console.log('btn' + id) shows btn 1 btn 2.
$('#update_btn').click(function (e){ gets called twice and then keeps on going to 3, 4, 5, ... until I refresh page.
To answer your question, I ask you (not a question but an imperative) to say to yourself: "How is the event listener function added to the element?"
The response is, via the click method in function body.
The response is, via the onclick property.
So you need to determine how you are going to attach the listener.
Variants of solution
Variant 1get the $('#update_btn').click( up to until the }) outside the body of editFaculty function and get rid of onclick in html!
Variant 2 get rid of click method there and use just the onclick.
I have this form that loads using jQuery $.ajax another form inside a container.
The list:
The loaded content within the container called form_load_dropdown_content:
On the left I have two small icons for edit and add. I want to use another ajax call to run specific PHP scripts to carry on the action desired.
I have the following problem:
when I click on each icon submit and respectively reset buttons display
when I click on the reset the both submit and reset are set to display: none
when I click on any the icon again, the click remains bind to the previous icon clicked before.
This is what I am doing:
form_load_dropdown_content.on("click", ".icon", function() {
// reusable selectors
var icon_box = $(".box_edit_icons");
var button_box = $(".box_buttons");
var submit_btn = $(".box_buttons input[type='submit']");
var reset_btn = $(".box_buttons input[type='reset']");
var option_value_input = $("input.option_value");
var option_order_input = $("input.option_order");
// common functions
button_box.show();
icon_box.hide();
if($(this).hasClass("ico_edit_small"))
{
// editing
form_load_dropdown_content.on("click", "input[type='reset']", function(event){
alert("reset from edit");
button_box.hide();
icon_box.show();
});
}
else if($(this).hasClass("ico_add_small"))
{
// adding
form_load_dropdown_content.on("click", "input[type='reset']", function(event){
alert("reset from add");
button_box.hide();
icon_box.show();
});
}
How can I differentiate between the two clicks, so that when I display the submit and reset from a specific icon type to run differentiated actions?
More clear:
when I click icon_add_small and then reset => output: 'reset from add'
then when I click icon_edit_small and then reset => output: 'reset from edit'... and so on without mixing the clicks.
I truly appreciate any help. I tried everything regarding stopping the propagation of the click... but nothing worked.
-----------------------------------------------------------
Edit:
I changed the if part to the following code and it works. Should I expect any problems for unbinding the click?
if($(this).hasClass("ico_edit_small"))
{
// editing
reset_btn.off("click");
reset_btn.click(function(event){
alert("reset from edit");
button_box.hide();
icon_box.show();
});
}
else if($(this).hasClass("ico_add_small"))
{
// adding
reset_btn.off("click");
reset_btn.click(function(event){
alert("reset from add");
button_box.hide();
icon_box.show();
});
}
Sorry for the weird title, I couldn't think of a better way to word it.
The situation is: I have a link that is generated by a complex system (can't be changed), which fires a JS function when clicked.
Instead, when the link is clicked I want a modal box to appear asking the user if they are sure. If they choose yes then the original JS function will run. If not then the modal closes and nothing happens.
Is it possible to unbind, capture, and rebind the original function like this?
Pseudo-code would be as follows, hopefully it's possible:
$('a.link').click(function(){
// *** Capture original JS function bound to link ***
// *** var agree = Run new function (open modal box) ***
if(agree === true){
// *** Run original function ***
}else{
// *** Close modal box ***
}
});
I'm probably thinking about this the wrong way, if there is a better one please let me know!
Your event handlers are going to fire in the order they are attached. If you can insert yourself in the first position of the queue, then on the callback parameter you can call e.stopImmediatePropagation(). It will prevent other events from firing. Set a variable record the response, and trigger a click on the link again.
var check = null; //store user's response
$(".preventable").on("click", function(e) {
if(check == null) {
e.stopImmediatePropagation();
alert("hijacked")
check = true; //confirm here
if(check)
$('.preventable').click();
}
}
$(".preventable").on("click", function(e) { alert("clicked2"); })
jsfiddle
I can't seem to get this to work in JavaScript. I've tried using plain old JavaScript and also JQuery but nothing seems to work.
Here's my situation:
I have this PopUp "Panel" and in it I have a Button. The button has an event listener for click and I want that handler to fire off a custom event that the Panel will listen for. This is because I need to handle all the logic of the button click in the Panel.
Here's what I'm doing:
Before I launch the Panel I call a constructor for my "Class":
function PopUpStageAsssignmentTaker(content) {
PopUpStage.call(this);
this.allPagesAdded = false;
this.questionsCreated = [];// will be an array of pages that will be submitted
this.listLabel = null;
addAssignmentTakerParts.call(this);
this.popUpDiv.addEventListener("assignmentTakingSubmitEvent", handleAssignmentSubmit, true);
function handleAssignmentSubmit(event) {
alert("YESSS!");
}
}
This does quite a bit but just know that in the call to PopUpStage it creates the div that represents the Panel and saves that in this.popUpDiv. So I add a event listener to this.popUpDiv listening for some custom event that I'm making up.
Later on I have code that creates the content in the Panel and we have something like this:
SubmitQuestionTakingPage.prototype.makeContent = function(question) {
var questionWrapper = getQuestionWrapper();
var submitDiv = document.createElement("section");
submitDiv.innerHTML = "Pressing Submit will cause this Assignment to be submitted and you will be unable to make any changes after that. If this " +
"Assignment is automatically graded you will receive a Grade upon clicking submit. If this Assignment is not automatically submitted you must wait" +
" for the creator of this Assignment to assign you a Grade. To continue, please press Submit.";
submitDiv.setAttribute("class", "separatedSmaller");
questionWrapper.appendChild(submitDiv);
var submitButton = document.createElement("input");
submitButton.setAttribute("type", "submit");
submitButton.setAttribute("class", "fancyButton");
submitButton.addEventListener("click", handleSubmitButtonClick);
questionWrapper.appendChild(submitButton);
return questionWrapper;
};
function handleSubmitButtonClick(event) {
var event = document.createEvent("Event");
event.initEvent("assignmentTakingSubmitEvent", true, true);
window.dispatchEvent(event);
// $(this).trigger("assignmentTakingSubmitEvent");
}
So we create some content and in it we create a button that has a listener for click. In the click handler you can see how I fire off the event.
Problem: I'm reading that this does not work in IE under version 9+. What can I do in to make it work in all browsers? Is there a way?
I'm performing a deleting action on a page when the user click on the confirmation button on a twitter bootstrap modal window button.
I have two buttons: one allow the user to cancel the action, and another one to confirm.
When the user clicks on the confirm delete button, when the modal is hidden, I perform my actions, so for example I can show an animation and actually delete the item.
If the user click on few items but his/her choice is the cancel button, when he/she clicks on the item he/she want to delete, the deletion is performed also on the elements where the choice has been to cancel.
Should not the 'hidden' event be detached from the element once it is performed?
I know I can detach the event chaining changing $('#confirmDeleteModal').on('hidden', function() { to $('#confirmDeleteModal').off('hidden').on('hidden', function() { but I really would understand why this happen. Am I missing something?
The code is:
$(document).ready(function(){
$('.delete').on('click', function() {
var itemID = $(this).data('product-id')
$('#confirmDeleteModal').modal('show');
$('#confirmDelete').on('click', function() {
$('#confirmDeleteModal').on('hidden', function() {
// Here I do my stuff to perform deletion
$('#result').append('This method has been called for ' + itemID + ' <br />' )
});
});
});
});
I hope I have exposed clearly my question. I prepared a JS Bin as well: http://jsbin.com/inulaw/5/edit
The problem here is that you are attaching additional listeners to the click and hidden events each time. To fix this, chain the jQuery .off('eventName') method before calling the .on('eventname') again.
Here's your code updated and working great in the JS Bin:
$(document).ready(function(){
$('.delete').on('click', function() {
var itemID = $(this).data('product-id')
$('#confirmDeleteModal').modal('show');
$('#confirmDeleteModal').off('hidden'); // must reset from previous
$('#confirmDelete').off('click').on('click', function() {
$('#confirmDeleteModal').on('hidden', function() {
// Here I do my stuff to perform deletion
$('#result').append('This method has been called for ' + itemID + ' <br />' )
});
});
});
});
EDIT: I moved the $('#confirmDeleteModal').off('hidden'); to above the click event so it resets whether or not the confirm is clicked.