i have written a basic commenting system which is a simple write to database form and it uses ajax as well.
The issue is that if i enter my message, and then spam send / the enter key it seems to stack up and then everything is written to the database multiple times.
My ajax is like so:
$(document).ready(function(){
$(document).on('submit', '.addcomment', function() {
var $targetForm = $(this);
$.ajax({
type: "POST",
url: "process/addcomment.php",
data: $targetForm.serialize(),
dataType: "json",
success: function(response){
if (response.databaseSuccess == true) {
$("#container").load("#container");
$targetForm.find('#addcommentbutton').attr("disabled", true);
}
else {
$ckEditor.after('<div class="error">Something went wrong!</div>');
}
}
});
return false;
});
});
The submit button does become disabled, but the form can still be entered via the enter keyboard button or even still with a mass spam of the submit button (which is supposed to be disabled)
Is there a way to 100% disable this form with jquery, until the success JSON message is received?
Anymore code just let me know!
In this case, i would not use delegation. I would instead bind the event directly to the form using .one since each form should submit only once (if that's the case.) If you instead only have one addComment form, then i question why you are using delegation in the first place.
$(commentForm).appendTo(selector).one("submit",function(e){
e.preventDefault(); // prevent this submit
$(this).submit(false); // prevent future submits
// submit data to server
})
Just keep track of if a request is in progress:
$(document).ready(function(){
var isSubmitting = false;
$(document).on('submit', '.addcomment', function() {
var $targetForm = $(this);
if (!isSubmitting) {
isSubmitting = true;
$.ajax({
...
success: function(response){
...
},
complete: function() { isSubmitting = false; }
});
}
});
There are lots of ways to handle this, but the best involves validating the data on the server end. You want to prevent people from overloading the database inadvertently (the "fat finger" problem) or deliberately (the bored script kiddie who decides to crash your server or fill your database with garbage).
The best solution:
Generate a one-time token when the page is requested (called a "nonce")
Post that nonce when you post the data
Only accept it on the server side if the nonce has never been used
This obviously requires you to keep track of a list of valid nonces, but it prevents any glitches or abuse of the send button.
Also, as others have pointed out, disable the button much earlier and only run the submit action handler once. That will help with the inadvertent double-clicks and so on, but you also need the nonce to prevent compulsive clickers or intentional misuse.
Can you do it like below:
$(document).ready(function(){
var isAjaxInProgress = null;
$(document).on('submit', '.addcomment', function() {
var $targetForm = $(this);
if(isAjaxInProgress === null || !$isAjaxInProgress ){
isAjaxInProgress = true;
$.ajax({
type: "POST",
url: "process/addcomment.php",
data: $targetForm.serialize(),
dataType: "json",
success: function(response){
if (response.databaseSuccess == true) {
$("#container").load("#container");
$targetForm.find('#addcommentbutton').attr("disabled", true);
}
else {
$ckEditor.after('<div class="error">Something went wrong!</div>');
}
isAjaxInProgress = false;
}
});
}
return false;
});
});
// declare a global ajax request variable
var is_request_sent = false;
function send_msg()
{
if(is_request_sent == false)
{
$.ajax({
type: "POST",
url: "process/addcomment.php",
data: $targetForm.serialize(),
dataType: "json",
success: function(result){
//alert(result);
is_request_sent = false;
},
error: function(a,b,c)
{
is_request_sent = false;
},
beforeSend: function(jqXHR, plain_jqXHR){
// set request object
is_request_sent = jqXHR;
// Handle the beforeSend event
},
complete: function(){
// update global request variable
is_request_sent = false;
// Handle the complete event
}
});
}
}
Related
I would like to validate a form with an AJAX request to the server and then swap the form html in the web browser with the form html from the server because this would be an easy implementation in theory. It is proving a nightmare though because the change event is triggered without the user interacting further after the first interaction which triggered the first change event. Consequently an infinite loop of AJAX requests to the server is happening.
The html form sits inside a div which has classes 'container mb-4'. This is the JS code -
var _cont = $('.container.mb-4')
var _form = $('.custom-form')
function ajax_validation(form) {
form.on('change', 'input, select, textarea', function() {
form_data = form.serialize()
$.ajax({
url: "/form/6/",
type: "POST",
data: form_data,
success: function(data) {
if(!(data['success'])) {
_cont.empty()
_cont.append(data['form_html'])
form = _cont.find('form')
ajax_validation(form)
}
},
error: function () {
form.find('.error-message').show()
}
});
})
}
ajax_validation(_form)
The change event I am assuming is triggered because the server returns a form input field with a different csrf token as the value to the previous input field - all other fields are the same. So an obvious solution would be to keep the same csrf token. But I want to understand why the JS code isn't working. I thought destroying the form would destroy the change event bound to it. So am at a loss to explain this infinite loop. How do I change this so I can just swap the form and not trigger another change event until the user really does change something?
It's not a good thing to use events in function no need to do that
Also your event here for input , select , textarea for serialize you need to select the closest() form
Try the next code
var _cont = $('.container.mb-4');
var _form = $('.custom-form');
_cont.on('change', 'form input,form select,form textarea', function() {
var ThisForm = $(this).closest('form');
var form_data = ThisForm.serialize();
$.ajax({
url: "/form/6/",
type: "POST",
data: form_data,
success: function(data) {
if(!(data['success'])) {
_cont.html(data['form_html']);
}
},
error: function () {
ThisForm.find('.error-message').show()
}
});
});
And logically if(!(data['success'])) { should be if(data['success']) {
First let's understand the issue that you have. You have a function called ajax_validation that is defining a change event on the form's elements which, on response will call ajax_validation. So, if any change happens on your elements, then a new request is sent to the server. So, if any value is changed, like a token, the request will be sent again. You could use a semaphore, like this:
var semaphore = true;
function ajax_validation(form) {
form.on('change', 'input, select, textarea', function() {
if (!semaphore) return;
semaphore = false;
form_data = form.serialize()
$.ajax({
url: "/form/6/",
type: "POST",
data: form_data,
success: function(data) {
if(!(data['success'])) {
_cont.empty()
_cont.append(data['form_html'])
form = _cont.find('form')
ajax_validation(form)
}
semaphore = true;
},
error: function () {
form.find('.error-message').show()
}
});
})
}
Something like this should solve your issue for the time being, but you should consider refactoring your code, because what you experience is well-known and is called callback hell.
Turns out the password field was coming back blank from the server - this django must do out of the box if the PasswordInput widget is used. So the form is replaced with a new form which lacks the password input from the before. The browser was then applying the autofill password value to the form which was triggering the change event.
This is my code now. It checks that the form_data about to be sent for validation really is different to before minus the csrf token which will be different.
It is based on Mohamed's answer -
var _cont = $('.container.mb-4');
var _form = $('.custom-form');
var prev_data = undefined
_cont.on('change', 'form input,form select,form textarea', function() {
var ThisForm = $(this).closest('form');
var form_data_wo_csrf = ThisForm.find("input, textarea, select").not("input[type='hidden']").serialize()
if(form_data_wo_csrf == prev_data) {
return
}
var form_data = ThisForm.serialize()
$.ajax({
url: "/form/6/",
type: "POST",
data: form_data,
success: function(data) {
if(!(data['success'])) {
_cont.html(data['form_html']);
prev_data = form_data_wo_csrf
}
},
error: function () {
ThisForm.find('.error-message').show()
}
});
});
I have tried everything. I find other questions but no answer solves my problem:
$(document).on('submit', 'form#formNuevoContacto', function(event) {
var $form = $(this);
var $accionActual = $form.find('#action');
$form.find('#action').val('validate');
$.post($form.attr("action"), $form.serialize(), function(response) {
if (response.resultValidation == "true") {
$form.submit(); // ==> INFINITE LOOP!
} else {
alert('Form is not valid!');
}
});
event.preventDefault();
});
I tried: $form.unbind().submit() but didn't work.
You could use a toggle to selectivelly pre-process the submit or otherwise let the browser do its job; In the edit bellow, just before issuing the second submit I place a variable in the form object called "isPreProcessing". As you can see, the first thing the code does is to check if this value is present, and if so, delegate the submit to the browser.
$(document).on('submit', 'form#formNuevoContacto', function(event) {
if(this.isPreProcessing) {
//allow for actual submit to run
this.isPreProcessing = false;
return;
}
var $form = $(this);
var $accionActual = $form.find('#action');
$form.find('#action').val('validate');
$.post($form.attr("action"), $form.serialize(), function(response) {
if (response.resultValidation == "true") {
//prevent the loop
this.isPreProcessing = true;
$form.submit();
} else {
alert('Form is not valid!');
}
});
event.preventDefault();
});
You should just do a single ajax call, and handle the error if it doesn't validate. If the server validates it, the server should then store it. It is messy code as you have it now, and it is also a waste of an ajax call/waste of time. You're posting the exact same data twice
$.ajax({
type: {most like put 'POST' here},
url: {action url},
data: {your form},
success: success_function(),
error: error_function()
});
I have ajax request:
<script>
$("#abc_form_submit").click(function(e) {
e.preventDefault();
//........
$.ajax({
type: "POST",
url: url,
dataType: 'json',
data: $("#abc_form").serialize(), // serializes the form's elements.
success: function(data)
{
if(data.success == 'false') {
// show errors
} else {
// SUBMIT NORMAL WAY. $("#abc_from").submit() doesnt work.
}
}
});
return false; // avoid to execute the actual submit of the form.
});
</script>
And php
.....
return $this->paypalController(params, etc...) // which should redirect to other page
.....
How should i make that ajax request if success, submit form normal way, because now if I redirect (at PHP) its only return response, but i need that this ajax request would handle php code as normal form submit (if success)
Dont suggest "window.location" please.
I would add a class to the form to test if your ajax has already occured. if it has just use the normal click funciton.
Something like:
$('form .submit').click(function(e) {
if (!$('form').hasClass('validated'))
{
e.preventDefault();
//Your code here
$.post(url, values, function(data) {
if (success)
{
$('form').addClass('validated');
$('form .submit').click();
}
});
}
}
Why don't you use a result variable that you update after a succesful AJAX request?
<script>
$("#abc_form_submit").click(function(e) {
e.preventDefault();
// avoid to execute the actual submit of the form if not succeded
var result = false;
//........
$.ajax({
type: "POST",
url: url,
dataType: 'json',
async: false,
data: $("#abc_form").serialize(), // serializes the form's elements.
success: function(data)
{
if(data.success == 'false') {
// show errors
} else {
// SUBMIT NORMAL WAY. $("#abc_from").submit() doesnt work.
result = true;
}
}
});
return result;
});
</script>
I've had this issue before where I needed the form to submit to two places, one for tracking and another to the actual form action.
It only worked by submitting it programatically when you put the form.submit() behind a setTimeout. 500ms seems to have done the trick for me. I'm not sure why browsers have trouble submitting the form programatically when they are attempting to submit them traditionally, but this seems to sort it out.
setTimeout(function(){ $("#abc_from").submit(); }, 500);
One thing to keep in mind though once it submits, that's it for the page, it's gone. If you still want whatever processes are running on the page to run, you will need to set the target of the form to _blank so that it will submit in a new tab.
I want to prevent multiple ajax calls (user holds enter key down or multi presses submit or other)
I'm thinking, the best way is to use a var with the previous form post values and compare them at each click/submit.. Is it the same? : Then do nothing
But I don't know how to go about it
Here is my javascript/jquery:
$('form').submit(function() {
$theform = $(this);
$.ajax({
url: 'validate.php',
type: 'POST',
cache: false,
timeout: 5000,
data: $theform.serialize(),
success: function(data) {
if (data=='' || !data || data=='-' || data=='ok') {
// something went wrong (ajax/response) or everything is ok, submit and continue to php validation
$('input[type=submit]',$theform).attr('disabled', 'disabled');
$theform.unbind('submit').submit();
} else {
// ajax/response is ok, but user input did not validate, so don't submit
console.log('test');
$('#jserrors').html('<p class="error">' + data + '</p>');
}
},
error: function(e) {
// something went wrong (ajax), submit and continue to php validation
$('input[type=submit]',$theform).attr('disabled', 'disabled');
$theform.unbind('submit').submit();
}
});
return false;
});
Not very creative with naming vars here:
var serial_token = '';
$('form').submit(function() {
$theform = $(this);
if ($(this).serialize() === serial_token) {
console.log('multiple ajax call detected');
return false;
}
else {
serial_token = $(this).serialize();
}
$.ajax({
url: 'validate.php',
type: 'POST',
cache: false,
timeout: 5000,
data: $theform.serialize(),
success: function(data) {
if (data=='' || !data || data=='-' || data=='ok') {
// something went wrong (ajax/response) or everything is ok, submit and continue to php validation
$('input[type=submit]',$theform).attr('disabled', 'disabled');
$theform.unbind('submit').submit();
} else {
// ajax/response is ok, but user input did not validate, so don't submit
console.log('test');
$('#jserrors').html('<p class="error">' + data + '</p>');
}
},
error: function(e) {
// something went wrong (ajax), submit and continue to php validation
$('input[type=submit]',$theform).attr('disabled', 'disabled');
$theform.unbind('submit').submit();
}
});
return false;
});
You could combine this with a timeout/interval function which aborts the submit, but the code above should just compare the data in the form
If you have some kind of submit button, just add a class 'disabled' to it when you start the ajax call, and check if it is present before trying to make the call. Remove the class when the server gives a response. Something like:
...
$theform = $(this);
$button = $theform.find('input[type=submit]');
if ($button.hasClass('disabled')) {
return false;
}
$button.addClass('disabled');
$.ajax({
....
},
complete: function () {
$button.removeClass('disabled');
}
});
...
I have a form that, when submitted, goes through the usual e.preventDefault() and sends an ajax request instead. However, if this ajax request returns a certain condition, I want the form to be submitted normally. How do I achieve this?
// Submit handler
$(".reserveer_form").submit(function(event){
event.preventDefault();
$.ajax({
url: $(this).attr("action"),
data: $(this).serialize(),
success: function(data) {
if($(".messagered",data).length > 0){
var errors = $(".messagered",data);
$(".gegevens").before(errors);
} else {
// SUBMIT THE FORM!
}
}
});
})
Invoke the native submit method on the form, so that it doesn't trigger the jQuery handler.
$.ajax({
context: this, // <-- set the context.
url: $(this).attr("action"),
data: $(this).serialize(),
success: function (data) {
if ($(".messagered", data).length > 0) {
var errors = $(".messagered", data);
$(".gegevens").before(errors);
} else {
this.submit(); // <-- submit the form
}
}
});
Since your comment says you change a form variable, you could start your submit handler by checking that same form variable. If it is changed, just return true. If not, continue with the current handler.
You can use the submit() method or forms:
$(".reserveer_form").submit(function(event){
event.preventDefault();
var form = this,
$form = $(form);
$.ajax({
url: $form.attr("action"),
data: $form.serialize(),
success: function(data) {
var errors = $(".messagered", data);
if (errors.length > 0){
$(".gegevens").before(errors);
} else {
form.submit();
}
}
});
})
However, this seems to be a strange ajax request. First, you send the form (serialized, via ajax) to the server, and when the response contains no errors you send it again? The server would process it twice (and act twice, depending on your form). Also, the user does not get a message that his input is already processed - he clicks "submit", and it always takes a time until it is visibly submitted (where he even could change some input).