Javascript not executing after .then - javascript

I'm trying to put recaptcha v3 on a form but inserting the token into a hidden input field doesn't work - on the first submission.
Here is the code that I doctored a little. I added an alert and stopped the sumbit to see what's happening. This code is in a separate bundle.js file.
var form = document.querySelector('#contact-form');
var inputs = form.querySelectorAll('input');
$(form).submit(function(e) {
e.preventDefault();
grecaptcha.ready(function() {
grecaptcha.execute('secret_key', {
action: 'my_action'
}).then(function(token) {
document.getElementById('tokenField').value = token;
alert('token is; ' + token); // inserted for troubleshooting
});
});
removeMessageBox();
validation();
var alerts = document.querySelectorAll('.alert-text');
if (alerts.length == 0) {
// document.forms['contact-form'].submit();
}
});
With this code as written, the alert shows a valid token but tokenField has no value. tokenField gets the token AFTER you press OK on the alert. What is the problem??? I have tried everything.
If you take out the alert and uncomment submit, tokenField is empty on submission.
Note that this script also calls validation() and removeMessageBox(), which removes validation error messages.
If validation() stops the submission for some reason, you can fix the problem, submit again and tokenField gets it value and everything works great - the second time.

As far as I can tell, the field is being set inside of a callback, while the other functions are running in the main function. You can either put all of the code into the callback function for the .then function, or use an async function.
Callback
$(form).submit(function(e) {
e.preventDefault();
grecaptcha.ready(function() {
grecaptcha.execute('secret_key', {
action: 'my_action'
}).then(function(token) {
document.getElementById('tokenField').value = token;
removeMessageBox();
validation();
var alerts = document.querySelectorAll('.alert-text');
if (alerts.length == 0) {
// document.forms['contact-form'].submit();
}
});
});
});
Async
$(form).submit(function(e) {
e.preventDefault();
grecaptcha.ready(async function() {
const token = await grecaptcha.execute('secret_key', {
action: 'my_action'
});
document.getElementById('tokenField').value = token;
removeMessageBox();
validation();
var alerts = document.querySelectorAll('.alert-text');
if (alerts.length == 0) {
// document.forms['contact-form'].submit();
}
});
});

Related

beforeinstallprompt triggers on every load

Beforeinstallprompt triggers on every load.
I have used the code here: https://developers.google.com/web/fundamentals/app-install-banners/
I am not using the The mini-info bar which i have dissabled by calling e.preventDefault();
The problem is that the showAddToHomeScreen(); is called on every load if the user does not click addToHomeScreen.
I want the showAddToHomeScreen(); function to be called only every month or so by storing information about the last "canceled" click in sessions or something similar. Isn't google suppose to do this on it's own?
This i found on the following link:
https://developers.google.com/web/updates/2018/06/a2hs-updates
You can only call prompt() on the deferred event once, if the user clicks cancel on the dialog, you'll need to wait until the beforeinstallprompt event is fired on the next page navigation. Unlike traditional permission requests, clicking cancel will not block future calls to prompt() because it call must be called within a user gesture.
window.addEventListener('beforeinstallprompt', function (e) {
// Prevent Chrome 67 and earlier from automatically showing the prompt
e.preventDefault();
// Stash the event so it can be triggered later.
deferredPrompt = e;
showAddToHomeScreen();
});
function showAddToHomeScreen() {
var prompt = document.querySelector(".a2hs-prompt");
prompt.style.display = "flex";
var open = document.querySelector(".a2hsBtn");
open.addEventListener("click", addToHomeScreen);
var close = document.querySelector(".a2hsBtn-close");
close.addEventListener("click", function() {
prompt.style.display = "none";
});
}
function addToHomeScreen() {
var prompt = document.querySelector(".a2hs-prompt");
// hide our user interface that shows our A2HS button
prompt.style.display = 'none';
if (deferredPrompt) {
// Show the prompt
deferredPrompt.prompt();
// Wait for the user to respond to the prompt
deferredPrompt.userChoice.then(
function (choiceResult) {
if (choiceResult.outcome === 'accepted') {
show_ad2hs_success_message();
}
deferredPrompt = null;
});
}
}
You have to define your own session and add expire date. This is simple with ajax. This is how i did:
Javascript:
$(document).ready(function() {
$.ajax({
url: '/update_session_addtohomescreen',
success: function (session_expired) {
if(session_expired=='True'){
showAddToHomeScreen();
}
},
error: function () {
alert("it didn't work");
}
});
});
This is wrapping the showAddToHomeScreen(); function
View
#csrf_exempt
def update_session_addtohomescreen(request):
if request.is_ajax():
number_of_days_till_expire = 1
now_in_secs = time.time()
if not 'last_session_coockie' in request.session or now_in_secs > request.session['last_session_coockie']+60:#number_of_days_till_expire*86400:
session_expired = True
request.session['last_session_coockie'] = now_in_secs
else:
session_expired = False
return HttpResponse(session_expired)
return None
You should though include csrf token in your request and also add the url to urls.py

.preventDefault() is not working in conditionals

I have a bit of a problem. When a form is submitted, I wan't to check some things about it, and if something is wrong, I want to prevent it from submitting and then show an error on the client side. Everything seems to work fine except the form keeps submitting. It even shows the error on client side for a split second before it submits.
$('#register').on('submit', function (e) {
e.preventDefault();
var username = $("#register-username"),
name = $("#register-name"),
email = $("#register-email"),
password = $("#register-password"),
confirmPassword = $("#register-confirmPassword");
checkUsername(function (res) {
if (res) {
checkEmail(function (res) {
if (res) {
this.submit();
} else {
clearErrors();
email.toggleClass('input-error');
}
});
} else {
clearErrors();
username.toggleClass('input-error');
}
});
});
function checkEmail(callback) {
$.get("/checkEmail/" + $('#register-email').val(), function (data) {
if ( data == undefined ) {
callback(true);
} else {
callback(false);
}
});
}
function checkUsername (callback) {
$.get("/checkUsername/" + $('#register-username').val(), function (data) {
if ( data == undefined ) {
callback(true);
} else {
callback(false);
}
});
}
function clearErrors () {
var arr = [
$("#register-username"),
$("#register-name"),
$("#register-email"),
$("#register-password"),
$("#register-confirmPassword")
];
arr.forEach(function(el) {
el.removeClass('input-error');
});
}
Update:
Now I am just confusing myself. checkUsername() returns undefined from my server, I know for a fact, but somehow it is reaching the 'else' statement where checkUsername() is called. I've added the rest of my code. Should clear some confusion.
The call to preventDefault is made from the anonymous callback function you're passing to checkUsername. If the anonymous function is called asynchronously, then it's too late to cancel the event.
Assuming the problem is due to asynchronous code not shown, an effective way is to use preventDefault for the jQuery submit handler and use native submit when all validation passes
Something like:
$('#register').on('submit', function (e) {
e.preventDefault();// prevent jQuery submit
// after all validation passes
this.submit();// submit native method won't trigger jQuery handler again
})
You need to return false from validation to stop the current submitting event, and then manually send the post request on success from when the callbacks have successfully returned.
Since the callback is running after the validation has returned from the server then you can actually affect whether a request is made. Where as the other solutions involve trying to change how the original submit event occurs which is no longer in scope since you've already sent requests to the server at this point.
$('#register').on('submit', function (e) {
var username = $("#register-username"),
name = $("#register-name"),
email = $("#register-email"),
password = $("#register-password"),
confirmPassword = $("#register-confirmPassword");
checkUsername(function (res) {
if (res) {
checkEmail(function (res) {
if (res) {
$.post('{insert form action here}', $(this).serialize());
} else {
clearErrors();
email.toggleClass('input-error');
}
});
} else {
clearErrors();
username.toggleClass('input-error');
}
});
return false;
});
You'll have to replace the {insert form action here}, and $(this).action might work in it's place, but I'm not sure.
So I built off of charlietfl's solution and assigned the native form to a variable, and then submitted it within an anon function.
$('#register').on('submit', function (e) {
e.preventDefault()
var username = $("#register-username"),
name = $("#register-name"),
email = $("#register-email"),
password = $("#register-password"),
confirmPassword = $("#register-confirmPassword"),
form = document.getElementById('register');
$.get("/checkUsername/" + username.val(), function (data) {
if (data) {
clearErrors();
username.toggleClass('input-error');
} else {
$.get("/checkEmail/" + email.val(), function (data) {
if ( data ) {
clearErrors();
email.toggleClass('input-error');
} else {
form.submit();
}
});
}
});
});
This works.

Use JavaScript to submit Ajax.BeginForm()

I'm typing this question away from my computer so I don't have the exact code, but the question might be straightforward enough without it.
When I have a submit button directly within an Ajax form and I click the button directly to submit, everything works fine, and as expected. The Ajax.Form POSTs back to the controller which returns a partial view that is rendered inside the current View that I have.
But what I need is for a button to be clicked in the Ajax.Form, and for a JavaScript function to run. The JavaScript function will do some vaildation which decides whether to submit the Ajax.Form or not.
I have tried putting 2 buttons in the Ajax.Form, a hidden submit button and a regular button. I used the onclick event of the regular button to call my JavaScript function which then called the click method of the hidden submit button. (I have also tried just submitting the Ajax.Form directly with document.forms[formname].submit() )
This sort of works.. But not correctly for some reason. The Ajax.Form POSTs back to the controller but when a partial view is returned from the controller, the partial view is the only thing rendered, and it is rendered as basic html with no css/bootstrap.
What is the difference between actually clicking the submit button and doing so programmatically?
How can Achieve what I am trying to do?
Edit
#using (Ajax.BeginForm("GetInstructorInfo", "Incident", FormMethod.Post, new AjaxOptions { OnBegin = "lookupInstructor();", UpdateTargetId = "InstructorInfo" }, new { #class = "form-inline", role = "form", #id = "instructorInfoForm", #name = "instructorInfoForm" }))
{
//code in here
}
Edit 2 / 3:
<script>
function lookupInstructor()
{
if ($('input[name="Instructors['+userInputInstructor+'].Username'+'"]').length > 0) //Don't allow user to enter multiple instances of the same Instructor
{
document.getElementById("InstructorUsername").value = ''; //clear textbox value
return false;
}
var userInputInstructor = document.getElementById("InstructorUsername").value;
$.ajax({
url: '#Url.Content("~/Incident/LookUpUsername")',
data: { userInput: userInputInstructor },
success: function (result) {
if (result.indexOf("not found") != -1){ //if not found
$("#InstructorNotFoundDisplay").show();
document.getElementById("InstructorUsername").value = ''; //clear textbox value
$('#InstructorInfo').empty();
return false;
}
else {
$("#InstructorNotFoundDisplay").hide();
return true;
}
}
});
}
</script>
You can use the OnBegin() ajax option to call a function that runs before the form is submitted (and return false if you want to cancel the submit). For example
function Validate() {
var isValid = // some logic
if (isValid) {
return true;
} else {
return false;
}
}
and then in the Ajax.BeginForm() options
OnBegin = "return Validate();"
Edit
Based on the edits to the question and the comments, you wanting to call an ajax function in the OnBegin() option which wont work because ajax is asynchronous. Instead, use jQuery.ajax() to submit you form rather than the Ajax.BeginForm() method (and save yourself the extra overhead of including jquery.unobtrusive-ajax.js).
Change Ajax.BeginForm() to Html.BeginForm() and inside the form tags replace the submit button with <button type="button" id="save">Save</button>and handle its .click() event
var form = $('#instructorInfoForm');
var url = '#Url.Action("GetInstructorInfo", "Incident")';
var target = $('#InstructorInfo');
$('#save').click(function() {
if ($('input[name="Instructors['+userInputInstructor+'].Username'+'"]').length > 0) {
....
return; // exit the function
}
$.ajax({
....
success: function (result) {
if (result.indexOf("not found") != -1) {
....
}
else {
$("#InstructorNotFoundDisplay").hide();
// submit the form and update the DOM
$.post(url, form.serialize(), function(data) {
target.html(data);
});
}
}
});
});

async html5 validation

For some reason html5 validation message is not shown when I'm using an async request.
Here you can see an example.
http://jsfiddle.net/E4mPG/10/
setTimeout(function() {
...
//this is not working
target.setCustomValidity('failed!');
...
}, 1000);
When checkbox is not checked, everything works as expected,
but when it is checked, the message is not visible.
Can someone explain what should be done?
I figured it out, turns out that the HTML5 validation messages will only popup when a form submit is in progress.
Here is the process behind my solution (when timeout is checked):
Submits the form
Sets the forceValidation flag
Sets the timeout function
When the timeout function is called, resubmit the form
If the forceValidation flag is set, show the validation message
Basically perform two submits, the first one triggered by the button, and the second triggered when the timeout function is called.
jsFiddle
var lbl = $("#lbl");
var target = $("#id")[0];
var forceValidation = false;
$("form").submit(function(){
return false;
});
$("button").click(function (){
var useTimeout = $("#chx").is(":checked");
lbl.text("processing...");
lbl.removeClass("failed");
target.setCustomValidity('');
showValidity();
if (forceValidation) {
forceValidation = false;
lbl.text("invalid!");
lbl.addClass("failed");
target.setCustomValidity('failed!');
showValidity();
} else if (useTimeout) {
setTimeout(function () {
forceValidation = true;
$("button").click();
}, 1000);
} else {
lbl.text("invalid without timeout!");
lbl.addClass("failed");
target.setCustomValidity('failed!');
showValidity();
}
});
function showValidity() {
$("#lbl2").text(target.checkValidity());
};
I am running on Chrome version 25.0.1364.172 m.

weird problem - change event fires only under debug in IE

I have form autocomplete code that executes when value changes in one textbox. It looks like this:
$('#myTextBoxId)').change(function () {
var caller = $(this);
var ajaxurl = '#Url.Action("Autocomplete", "Ajax")';
var postData = { myvalue: $(caller).val() }
executeAfterCurrentAjax(function () {
//alert("executing after ajax");
if ($(caller).valid()) {
//alert("field is valid");
$.ajax({ type: 'POST',
url: ajaxurl,
data: postData,
success: function (data) {
//some code that handles ajax call result to update form
}
});
}
});
});
As this form field (myTextBoxId) has remote validator, I have made this function:
function executeAfterCurrentAjax(callback) {
if (ajaxCounter > 0) {
setTimeout(function () { executeAfterCurrentAjax(callback); }, 100);
}
else {
callback();
}
}
This function enables me to execute this autocomplete call after remote validation has ended, resulting in autocomplete only when textbox has valid value. ajaxCounter variable is global, and its value is set in global ajax events:
$(document).ajaxStart(function () {
ajaxCounter++;
});
$(document).ajaxComplete(function () {
ajaxCounter--;
if (ajaxCounter <= 0) {
ajaxCounter = 0;
}
});
My problem is in IE (9), and it occurs only when I normally use my form. Problem is that function body inside executeAfterCurrentAjax(function () {...}); sometimes does not execute for some reason. If I uncomment any of two alerts, everything works every time, but if not, ajax call is most of the time not made (I checked this by debugging on server). If I open developer tools and try to capture network or debug javascript everything works as it should.
It seems that problem occurs when field loses focus in the same moment when remote validation request is complete. What I think it happens then is callback function in executeAfterCurrentAjaxCall is executed immediately, and in that moment jquery validation response is not finished yet, so $(caller).valid() returns false. I still do not know how alert("field is valid") helps in that scenario, and that could be sign that I'm wrong and something else is happening. However, changing executeAfterCurrentAjaxCall so it looks like this seems to solve my problem:
function executeAfterCurrentAjax(callback) {
if (ajaxCounter > 0) {
setTimeout(function () { executeAfterCurrentAjax(callback); }, 100);
}
else {
setTimeout(callback, 10);
}
}

Categories

Resources