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.
Related
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.
I have an ajax based application (ie no page 'reloads` when getting new data).
Initially I wanted to find a way to prevent navigation when unsaved data was present on a form.
I came across window.onbeforeunload.
That didn't work when clicking a links (where content is loaded via ajax and pop/push state changes the url).
I added some handling of the a links but need to use the default window.onbeforeunload to cover the standard means of leaving a page (ie manually entering a new URL or using the back/forwards buttons).
The code below works for:
a links
page refresh
manually entering a new url
But is not triggering window.onbeforeunload when using the back button (in Chrome and Firefox).
Is there something awry with the implementation below or is window.onbeforeunload not meant to be triggered when using the back button?
var save_state = true;
// on entering data into an input field, the save button fades in
// and the save_state changes
$(document).on('keypress', '.class1 input', function() {
if (save_state) {
$(".save_button").fadeIn();
save_state = false;
};
// bind the click event to 'a' (overiding normal link behaviour)
$( "a" ).bind( "click", function(e) {
if (save_state == false) {
e.preventDefault();
alert("Save before leaving.");
// stop the other 'a' bound handlers from being triggered
e.stopPropagation();
return;
}
});
// also cover standard actions when user tries to leave page
// (back/forward or entering a new url manually etc)
window.onbeforeunload = function() {
return 'Save before leaving.';
};
});
// when clicking save, fade out the button and revert the save_state
$(document).on('click', '.save_button button', function(e) {
e.preventDefault();
$(this).parent().fadeOut();
save_state = true;
// 'unbind' onbeforeunload
window.onbeforeunload = null;
});
Edit:
After reading this post, I think it is based on the ajax nature of the app:
As long as you stay within your app, because it's a single-page app,
it doesn't by definition unload, so there's no beforeunload event.
So I think I may need to look at other ways to trigger the event on back/forwards buttons.
I looked in to triggering window.unload on popstate but didn't have any luck with that.
I ended up changing the logic so that as soon as changes were made, they were saved in the database.
I was using selectize.js for the form input handling and just added some extra calls to it.
In selectize.js (here):
if ($target.hasClass('create')) {
self.createItem();
} else {
value = $target.attr('data-value');
became:
if ($target.hasClass('create')) {
self.createItem();
$(".saved_message_div").fadeIn(400).delay(2000).fadeOut(400); // new
updateDatabaseFunction(); // new
} else {
value = $target.attr('data-value');
$(".saved_message_div").fadeIn(400).delay(2000).fadeOut(400); // new
updateDatabaseFunction(); // new
AND (here):
while (values.length) {
self.removeItem(values.pop());
}
self.showInput();
became:
while (values.length) {
self.removeItem(values.pop());
}
$(".saved_message_div").fadeIn(400).delay(2000).fadeOut(400); // new
updateDatabaseFunction(); // new
self.showInput();
And in my own script, the prompt for deleting selectize.js items stayed the same:
onDelete: function(values) {
confirm(values.length > 1 ? 'Are you sure you want to remove these ' + values.length + ' items?' : 'Are you sure you want to remove "' + values[0] + '"?');
I have a confirm box on my colorbox("are you sure you want to leave?").
This triggers when i close the popup. This works when i click on the "cboxClose" div on the popup.
I am trying to show this confirm box on a button click. But the popup just closes right away without showing the confirm box.
My question is how do i trigger the the confirm box when i click on a cancel button. i tried several ways
//This just closes the pop up without showing the confirm box
$('#btnCancel').click(function () {
parent.$.colorbox.close(); });
//doesn't work
$('#btnCancel').click(function () {
$('#cboxClose').click()
});
COLORBOX
onComplete: function () {
$("#cboxClose").click(function (e) {
// stop any other script from firing
e.stopPropagation();
if (confirm('are you sure you want to leave?')) {
$.colorbox.close();
// ensure that the binding is removed when closed
$("#cboxClose").unbind();
}
});
} // close oncomplete
The issue here is that colorbox registers a click handler on the cboxClose element. As a result, neither stopping bubbling nor preventing the click (by returning false in a click handler) will have any effect because the colorbox handler is already registered. The only way of stopping that handler from being run is to unbind it. However, to do that you need a reference to the handler, which you won't get without modifying the colorbox code.
In any case, that's what's going on and why the code you have above doesn't work. Another option for you would be to override the colorbox close function (which is the public colorbox method that is called by colorbox's close button handler). All you need is this:
$.colorbox._close = $.colorbox.close;
$.colorbox.close = function() {
if(confirm("Close?")) {
$.colorbox._close();
}
}
The down side (which may not be an issue in your situation) is that this will affect all colorboxes on the page.
I solved this issue by making this method and binding it to the cancel button
var originalClose = $.colorbox.close;
$.colorbox.close = function (e) {
var response;
var formChanged = localStorage.getItem("isFormChanged");
var saveClicked = localStorage.getItem("saveClicked");
if (formChanged == "true" && saveClicked == "false") {
response = confirm('Do you want to close this window? All your changes will not be saved');
if (!response) {
return
}
}
originalClose();
};
<input type="button" value="Cancel" id="btncancel" onclick="parent.$.colorbox.close()"/>
Maybe I'm on the wrong track...
The setup:
I have a rather complex full dojofied web application. The important part for this question is a longish form in the central region of a dijit.layout.BorderContainer with a navigation tree and some action buttons in the other regions.
What I want to do:
If the user did enter data and did not save, they should get a warning message if he is going to leave the form (if he navigates away, klicks the "new Element" button,...). For a better user experience, I wanted to give a modal dialog with the options "save", "leave anyway", "cancel".
May idea was to use the onBlur event of the form, stop all other events (most likely an onClick on some other widget), check for changes, if there are changes, display the dialog, otherwise let the other events continue.
I do not want to add a checkChanges method to all non-form active elements!
For a first test I just tried to stop the events...
This works
<div id="formArea" dojoType="dijit.form.Form" encoding="multipart/form-data" action="" class="ContentPane" region="center">
<script type="dojo/connect" event="onBlur" >
alert("I don't think so");
</script>
</div>
...but it's ugly and I can't easily continue
This doesn't
<div id="formArea" dojoType="dijit.form.Form" encoding="multipart/form-data" action="" class="ContentPane" region="center">
<script type="dojo/connect" event="onBlur" args="e">
console.log("blur"); // ok
e.preventDefault();//event.stopt(e)//return false //<--neither of these
</script>
</div>
the problem is that if I click on a button outside of the form, the onBlur triggers, but I can't stop the onClick on the button.
I know that onBlur doesn't deliver an event object - so the e.something can't really work...
Is there any way to catch the onClick on the other element?
Pause button event listener(s) in form's onBlur if data are not saved.
See it in action: http://jsfiddle.net/phusick/A5DHf/
You have some button event listeners, register them via on.pausable(node, event, callback) instead of on():
var b1Handler = on.pausable(button1Node, "click", function() {
console.log("b1.onClick");
});
var b2Handler = on.pausable(button2Node, "click", function() {
console.log("b2.onClick");
});
Gather handlers into an array:
var handlersToPause = [b1Handler, b2Handler];
Add onBlur event listener:
on(form, "blur", function(e) {
if (this.isDirty()) {
// if data are not saved, pause button event listeners
handlersToPause.forEach(function(handler) {
handler.pause();
});
// display modal dialog here
}
});
Add e.g. onFocus event listener to resume button event listeners:
on(form, "focus", function(e) {
handlersToPause.forEach(function(handler) {
handler.resume();
});
});
Please note, that handler.pause() is pausing an onclick listener, not an event. The onclick event is waiting in the Event queue and therefore is not accessible in the execution time of onblur.
I would work out some more robust solution, but this is quick and answers your question. Anyway, have a look at dojo/aspect and its around advice to call your checkChanges without the need to change all non-form active elements.
there is afaik only confirm('question?') that will 'deadlock' the events of your page like that.
I have made a similar setup though, the way I came around this (except if user enters url in addressbar and hits enter) was a popup dialog whenever the navigation tree is clicked, sending user to a new view. Consider:
----------------------------------------
| Nav 1 | Asset1 ( view controller ) |
| Nav 2 | Asset2 ( hidden ) |
----------------------------------------
Nav 1 is the default onload view, Asset 1 is loaded, contains a 'setup page' form or similar and can be changed. The trick is, Asset1 and Asset2 is derivative from AbstractAsset which in turn is a simple ContentPane extension.
In AbstractAsset is a 'must-override-for-functionality' function called isDirty
var Viewcontroller = declare("AbstractAsset", [dijit.ContentPane], {
isDirty: function() { return false; }
});
declare("Asset1", [Viewcontroller], {
startup: function() {
// sets up form
...
// and references inputfields to 'this'
this.inputfields = this.form.getChildren();
// and saves (clones) the state of each field
var self = this;
this.inputfields.forEach(function(inputWidget) {
self.states[inputWidget.id] = inputWidget.get("value");
});
},
isDirty: function() {
var self = this;
var dirty = false;
this.form.getChildren().some(input) {
if(self.states[input.id] != input.get("value")) {
dirty = true;
return false; // breaks .some loop
}
return true;
});
return dirty;
}
})
Then in turn, every navigation click must call the currently visible view controller's isDirty function in order to procede. Lets say user clicks the nav-tree (dijit.Tree) row node Nav 2.
var navigation = dojo.declare("NavigationController", [dijit.Tree], {
currentView : null,
onLoad: function() {
// start Asset1 in viewNode by default
this.currentView = new Asset1({ }, this.viewNode);
},
onClick : function() {
if(this.currentView.isDirty()) alert("I Dont Think So");
else {
this.loadFunction(this.model.selection.getSelected());
}
}
});
This is the general idea of implementing the on-unload-check, you Will need to hook any onClick events through your 'master application controller' to determine what should happen. Check this application which serves as cms navigation controller and its page.js:587 for isDirty example
For some weird reason i'm getting my confirm box coming up twice. here is my code:
$(".DeleteComment").live("click", function(){
var CommentID = $(this).attr("rel");
var confirm
if (!confirm('Are you sure you want to permanently delete this comment?')){
return false;
}else{
$(this).html("loading").css("color", "#999");
//AJAX HERE
return false;
}
});
Do you load any content dynamically (via ajax)? It could be the case that the click event is bound to the same element twice resulting in the double confirmation.
It happens when we bind event on the elements which are loaded dynamically via AJAX
So for example we are loading some dynamic html content (e.g. dynamic content in modal) on click of the edit form button,
And in that content if we have binded click event on some button e.g. delete button, then every time we click on edit form button, it binds the click event to delete button every time,
And if you have set confirm box on click event of delete button then, it will ask you as many time as it was binded for that click event means here if we have clicked edit form button 5 times then it will asks for your confirmation 5 times.
So for solving that issue you can unbind the event every time before binding event to dynamically loaded element as following :
$(document).off('click', '.DeleteComment').on('click', '.DeleteComment', function () {
if (confirm('Are you sure you want to permanently delete this comment?')){
//Delete process
return true;
}
return false;
}
Or Another way to solve this problem is to add your script in main page, means static page not in dynamically loaded one.
try this:
$_blockDelete = false;
$(".DeleteComment").live("click", function(event){
event.preventDefault();
//event.stopPropagation(); // it is not necessary
if (!$_blockDelete)
{
$_blockDelete =true;
var rconfirm = confirm('Are you sure you want to permanently delete this comment?');
if (rconfirm)
{
$(this).html("loading").css("color", "#999");
var CommentID = $(this).attr("rel");
//AJAX HERE
//return the value "false" the variable "$_blockDelete" once again ajax response
}
}
});
Did you try removing that not-used var confirm?