Javascript function on submit button - Wait for it to complete - javascript

I want to trigger a function when the submit button is pressed on the form and wait for the javascript function to complete, then continue the form submission. I dont want the form to submit before the javascript function has completed.**
This is what I have at the moment:
http://jsfiddle.net/njDvn/68/
function autosuggest() {
var input = document.getElementById('location');
var options = {
types: [],
};
var autocomplete = new google.maps.places.Autocomplete(input, options);
}
<!-- Get lat / long -->
function getLatLng() {
var geocoder = new google.maps.Geocoder();
var address = document.getElementById('location').value;
geocoder.geocode({
'address': address
}, function (results, status) {
if (status == google.maps.GeocoderStatus.OK) {
var latLng = results[0].geometry.location;
$('#lat').val(results[0].geometry.location.lat());
$('#lng').val(results[0].geometry.location.lng());
} else {
alert("Geocode was not successful for the following reason: " + status);
}
});
}
<!-- Load it -->
window.onload = autosuggest;

You can intercept the form submission, abort it and send the geocoding request to Google.
When the server responds, you can re-submit the form from the callback (or display an error in case of failure).
Store the state of the request somewhere (for the sake of simplicity, I'm using a global variable in my example). In this case, it's just a flag that indicates whether or not the geocoding request was completed successfully (so that now, when the form is submitted and the listener is re-triggered, it will know not to re-send the geocoding request).
http://jsfiddle.net/njDvn/75/
Feel free to remove the console logging.
You may also want to make the lat/long fields hidden.
var GeoCoded = {done: false}; // this holds the status of the geo-coding request
$(document).ready(function(){
autosuggest(); // place your auto-suggest (now called autocomplete) box
$('#myform').on('submit',function(e){
if(GeoCoded.done)
return true;
e.preventDefault();
console.log('submit stopped');
var geocoder = new google.maps.Geocoder();
var address = document.getElementById('location').value;
// disable the submit button
$('#myform input[type="submit"]').attr('disabled',true);
// send the request
geocoder.geocode({
'address': address
},
function (results, status) {
// update the status on success
if (status == google.maps.GeocoderStatus.OK) {
var latLng = results[0].geometry.location;
$('#lat').val(results[0].geometry.location.lat());
$('#lng').val(results[0].geometry.location.lng());
// if you only want to submit in the event of successful
// geocoding, you can only trigger submission here.
GeoCoded.done = true; // this will prevent an infinite loop
$('#myform').submit();
} else { // failure
console.log("Geocode was not successful for the following reason: " + status);
//enable the submit button
$('#myform input[type="submit"]').attr('disabled',false);
}
});
});
});

First you need to prevent the form to be submitted, add a return false at the end of getLatLng() function.
Then when the geocoding is done, submit the form manually with document.getElementsByTagName('form')[0].submit()
Here's an updated jsfiddle: http://jsfiddle.net/njDvn/70/

myFunction = new function(callback) {
$.ajax({...}).done(callback());
}
myFunction(new function() {
getLatLng();
});
Here you need to call myFunction onSubmit event.

As MasterAM has stated, all you need do is the following:
/// I've included the following function to allow you to bind events in a
/// better way than using the .onevent = function(){} method.
var addEvent = (function(){
if ( window.addEventListener ) {
return function(elm, eventName, listener, useCapture){
return elm.addEventListener(eventName,listener,useCapture||false);
};
}
else if ( window.attachEvent ) {
return function(elm, eventName, listener){
return elm.attachEvent('on'+eventName,listener);
};
}
})();
/// add another window.onload listener
addEvent(window,'load',function(){
/// find the form, obviously should use whatever id you have on your form
var form = document.getElementById('my_form');
form.preventSubmit = true;
/// add our onsubmit handler
addEvent(form,'submit',function(e){
/// only stop the form if we haven't already
if ( form.preventSubmit ) {
/// place/call whatever code you need to fire before submit here.
alert('submit was blocked!');
/// after that code is complete you can use the following
form.preventSubmit = false;
form.submit();
/// return false prevents the submit from happening
return false;
}
});
});

Related

Seeking more elegant solution to preventDefault() dilemma

I have a jQuery form-submission routine that has an input integrity check in ERROR_CHECK.PHP that relies on GET variables passed to it for inspection. If the values passed to it are malformed, then an alert box appears that explains the error and how the form data should be remedied. This alert box will need to pop up until the form data is no longer malformed, at which point that data is used for repopulating data on the page.
Thus, in the jQuery routine I'm at the mercy of our friend preventDefault(), and I have found a solution that does work, but not elegantly. The variable allowSubmit is initialized as FALSE and remains that way—with preventDefault() also in effect—until the form data passes the integrity check, at which point allowSubmit switches to TRUE...but that only happens with the submission of the correctly-formed input data. This means the user must submit the form a SECOND TIME in order for the form data to be used to replace data on the page...and that, of course, is not a solution (press the submit button twice?)
However, by dynamically submitting the form (here, with the $('#my_form').submit() statement) immediately after resetting allowSubmit to TRUE, I've submitted the form again, thereby allowing the user to submit correctly-formatted data ONCE, as it should be from the get-go.
This is obviously a band-aid solution and not elegant. Can anyone see a more elegant way to structure this? (I'm working with jQuery fashioned by another developer, and this occurs in the midst of a longer self-calling JQuery function, and I have to work with it on its own terms, lest I have to refashion all other parts of the larger function in which it occurs.
Here's a distillation of the code (with self-describing variables, etc.), which works as described, although not as elegantly as I'd like:
var allowSubmit = false;
$('#my_form').on('submit', function(e) {
if (!allowSubmit) {
e.preventDefault();
// Check to see if input data is malformed:
$.get('error_check.php', { new_element_name: $('#new_element_name').val() }, function(data) {
if (data != 0) {
alert("An Error Message that explains what's wrong with the form data");
} else {
allowSubmit = true;
// The line below--an auto-submit--is needed so we don't have to press the submit button TWICE.
// The variable allowSubmit is set to TRUE whenever the submitted form data is good,
// but the code suppressed by e.preventDefault() won't execute until the form is
// submitted a second time...hence the need for this programmatic form submission here.
// This allows the user to correct the errant form data, press the submit button ONCE and continue.
$('#my_form').submit();
}
});
}
$('#element_name').val($('#new_element_name').val());
});
What you are doing is okay, your other options might be to write a click handler for a generic button and submit the form through that event after validation, then you wont need to preventDefault as you won't be preventing any kind of submit action. Another solution might be to re-trigger the event after validation.
$("button").click(function() {
$("#my_form").submit();
});
...
allowSubmit = true;
// alternatively
jQuery( "body" ).trigger( e );
...
The callback solution you have doesn't seem unreasonable. I agree with #scott-g that a generic button click event handler would probably be your best bet. A more testable way to write what you have here may be:
var formView = {
$el: $('#my_form'),
$field: $('#element_name'),
$newField: $('#new_element_name'),
$submitBtn: $('#btn-submit')
}
var handleSubmit = function() {
var formData = formView.$field.val();
remoteVerify(formData)
.done(formView.$el.submit)
.done(updateForm)
.fail(handleVerificationError);
};
var remoteVerify = function(formData) {
var deferred = $.Deferred();
var url = 'error_check.php';
var data = { new_element_name: formData };
$.get(url, data)
.done(handleRequest(deferred))
.fail(handleRequestErr);
return deferred;
};
var handleRequest = function(deferred) {
return function (data, jqxhr) {
if (data != 0) {
deferred.reject(jqxhr, "An Error Message that explains what's wrong with the form data");
} else {
deferred.resolve(data);
}
}
};
var handleRequestErr = function() {
// error handling
}
var updateForm = function () {
formView.$field.val(formView.$newField.val());
}
var handleVerificationError = function (jqxhr, errMsg){
alert(errMsg);
}
formView.$submitBtn.on('click', handleSubmit)
You could try using an async: false setting using $.ajax (I don't know what your php is returning, so I am just "pretending" it's a json array/string like so echo json_encode(array("response"=>$trueorfalse));):
<script>
$('#my_form').on('submit', function(e) {
var valid_is = true;
// Check to see if input data is malformed:
$.ajax({
async: false,
url: 'error_check.php',
type: 'get',
data: { new_element_name: $('#new_element_name').val() },
success: function(response) {
var Valid = JSON.parse(response);
if(Valid.response != true) {
alert("An Error Message that explains what's wrong with the form data");
valid_is = false;
}
}
});
if(!valid_is)
e.preventDefault();
$('#element_name').val($('#new_element_name').val());
});
</script>
If you use async: false it runs the script in order and waits to execute the rest of the script until after it receives a response. Scott G. says you can do it with what you have with some slight modifications so I would try that first.

Universal button handler in javascript

I have a lot of buttons on my web app that request and post data to PHP to retrieve and update a database. I am struggling to create a universal way to prevent multiple button clicks when submitting forms, because I am using AJAX and Jquery.
This is my current implementation but I can't even tell if it works. It seems to work 99% of the time.
In my common functions.js file I have this function which is in the global scope
var canClick = true;
function buttonWithPromise(promise){
if(!canClick) return;
canClick = false;
promise.done(function(){
canClick = true;
});
}
Then any time I attach a .click to a dom element I do it like this:
$('body').on('click', '.table > .row', function(){
var nbr = $(this).attr('nbr');
buttonWithPromise(get_count(nbr));
});
And some function that might be called will have a deferred object.
function get_count(){
var defer = $.Deferred();
var options = "getCount"
Query.init(options)
.fetchData(function(data){ //Ajax data request
if(data){
}
defer.resolve();
});
return defer.promise();
}
Since this only sometimes works, I can tell it's wrong. Any advice for improvements?
Everything in Javascript is an object, yes? So why not:
$('body').on('click', '.button', function()
{
// Set default value of property
if(typeof this.isClicked === 'undefined')
this.isClicked = false;
// Check if button is working
if(this.isClicked)
{
// Send error to console if button is busy
console.log('Cannot click as a network action is occuring!');
}else
{
// Begin new network action if button is not busy
var self = this;
console.log('Begin network for: ' + $(this).text());
this.isClicked = true;
setTimeout(function()
{
// Reset button state once network action is done
console.log('End network for: ' + $(self).text());
self.isClicked = false;
//Call any callbacks/promises here
}, 5000);
}
});
Fiddle:
http://jsfiddle.net/mdLfug1t/
NOTE: I'm using setTimeout to simulate an ajax request
EDIT: Let me put this more into context:
function buttonWithPromise(promise)
{
if(typeof promise.canClick === 'undefined')
promise.canClick = true;
if(!promise.canClick) return;
promise.canClick = false;
promise.done(function()
{
promise.canClick = true;
});
}
The problem that you're running into is that "canClick" is global and so gets modified by every promise. You need to make it a property of a promise so that you can create infinite promises, each with their own instance of canClick.

running a function after a google map call has finished

I'm trying to collect location data from my users as they're entering other details in a form. So I have a google map which users can move a marker about on but I also have an input which they can type a location into.
When they click to submit the form, I want to first geocode the location they have typed and update the hidden lat lng inputs in my form before the submit actually takes place.
I know there's lots of info online but I've read loads of tutorials but I'm struggling to understand or at least to apply the tutorials to my situation.
So when a user clicks submit I want to run geocode and only submit the form when geocode is definitely finished.
$('.submitButton').click(function () {
geocode();
$("#searchForm").submit();//Then submit when geocode finishes
});
And the geocode function
function geocode(){
geocoder.geocode({
address: $('#address').val()
}, function(results, status){
if(status == google.maps.GeocoderStatus.OK){
var geoPoint = new google.maps.LatLng(results[0].geometry.location.lat(), results[0].geometry.location.lng());
map.setCenter(geoPoint);
circle.setCenter(geoPoint);
marker.setPosition(geoPoint);
$('#lat').val(geoPoint.lat());
$('#lng').val(geoPoint.lng());
} else{
alert("Can not geolocate this address.");
}
});
}
Can someone explain to me what I need to do in simple javascript dummy language? THanks
Because Geocoding service is asynchronous, geocode() will return almost immediately and $("#searchForm").submit() will submit what is available and that will be wrong (old or undefined data).
You have to move form submit call to geocode() function just after settings of lat/lng values:
...
marker.setPosition(geoPoint);
$('#lat').val(geoPoint.lat());
$('#lng').val(geoPoint.lng());
$("#searchForm").submit();
} else{
...
If, as you said in comment, geocode() function is also used somewhere else then you will have to write another function with similar functionality or prepare two different callback function.
You may pass an optional argument to geocode, e.g. a function that will be executed on success:
$('.submitButton').click(function (e) {
e.preventDefault();
//call geocode and pass the desired function as argument
geocode(function(){$("#searchForm").submit();});
return false;
});
function geocode(fnc){
geocoder.geocode({
address: $('#address').val()
}, function(results, status){
if(status == google.maps.GeocoderStatus.OK){
var geoPoint = new google.maps.LatLng(results[0].geometry.location.lat(),
results[0].geometry.location.lng());
map.setCenter(geoPoint);
circle.setCenter(geoPoint);
marker.setPosition(geoPoint);
$('#lat').val(geoPoint.lat());
$('#lng').val(geoPoint.lng());
//check if the fnc-argument is a function,
//when it does, execute the function
if($.type(fnc)==='function'){
fnc();
}
} else{
alert("Can not geolocate this address.");
}
});
}

Reusing same javascript with slightly different application

I have a problem where I have some code which I want to apply to two slightly different applications, but 75% of the code will be the same. This is where I am with things:
http://jsfiddle.net/spadez/H6SZ2/6/
$(function () {
var input = $("#loc"),
lat = $("#lat"),
lng = $("#lng"),
lastQuery = null,
lastResult = null, // new!
autocomplete;
function processLocation(callback) { // accept a callback argument
var query = $.trim(input.val()),
geocoder;
// if query is empty or the same as last time...
if( !query || query == lastQuery ) {
callback(lastResult); // send the same result as before
return; // and stop here
}
lastQuery = query; // store for next time
geocoder = new google.maps.Geocoder();
geocoder.geocode({ address: query }, function(results, status) {
if( status === google.maps.GeocoderStatus.OK ) {
lat.val(results[0].geometry.location.lat());
lng.val(results[0].geometry.location.lng());
lastResult = true; // success!
} else {
alert("Sorry - We couldn't find this location. Please try an alternative");
lastResult = false; // failure!
}
callback(lastResult); // send the result back
});
}
var ctryiso = $("#ctry").val();
var options = {
types: ["geocode"]
};
if(ctryiso != ''){
options.componentRestrictions= { 'country': ctryiso };
}
autocomplete = new google.maps.places.Autocomplete(input[0], options);
google.maps.event.addListener(autocomplete, 'place_changed', processLocation);
$('#search_form').on('submit', function (event) {
var form = this;
event.preventDefault(); // stop the submission
processLocation(function (success) {
if( success ) { // if the geocoding succeeded, submit the form
form.submit()
}
});
});
});
When the submit form button is pressed it will do the following (this works already):
Check if result is already geocoded from autosuggest click
If not, geocode
Submit the form if successful
When the Geocode button is pressed I want it to do the following (this doesn't work):
Check if result is already geocoded from autosuggest click
If not, geocode
So my question is, how can I make it so I can use the same code for both scripts without having to duplicate it twice. My idea was to do the following:
Have a function for the "submit form" button which includes submitting the form
Have a function for the geocode button which does not include submitting the form
I would rather use a function for each button rather than using some kind of flag, but I am completely stuck on how to implement this, every time I work on it I end up breaking the script completely. I am new to JavaScript.
If the only difference is that one submits and the other does not, allow a callback to be passed into the function, and the one that submits can pass a submit to the function.

Jquery Logic - Can't efficiently achieve flag check

I am new to javascript but I am having a hard time creating code which can achieve this logic:
I am trying to achieve the following logic:
If a user enters text in location box and DOES click autosuggestion Then geocode and set coded flag to "True"
If a user enters text in location DOES NOT click autosuggestion Keep coded flag to "False"
If a user changes the text in location box Make sure "coded" flag is set to "false" - Since the text doesnt match the coordinates we
fetched for it now
If a user clicks search button Check "coded" flag If true, submit form If false, process geocode, and if successful, submit form
This is how far I got before I got stuck: LINK
Even when I click on an autosuggested link, which initiates a geocode, and then do not change the content in the input box, when I hit search it says "Location not geocoded - Process location first". I feel like my logic in the programming is wrong but I am not sure how to fix it without lots of ugly repetitive code.
FULL CODE
geocode();
// SET COOKIE FOR TESTING PURPOSES
$.cookie("country", "uk");
// GEOCODE FUNCTION
function geocode() {
var coded = false;
console.log(coded);
var input = document.getElementById('loc');
var options = {
types: ['geocode']
};
var country_code = $.cookie('country');
console.log(country_code);
if (country_code) {
options.componentRestrictions = {
'country': country_code
};
}
var autocomplete = new google.maps.places.Autocomplete(input, options);
google.maps.event.addListener(autocomplete, 'place_changed', function() {
processLocation();
});
// ON SUBMIT - WORK OUT IF WE ALREADY HAVE THE RESULTS FROM AUTOCOMPLETE FUNCTION
$('#searchform').on('submit', function(e) {
console.log(coded);
e.preventDefault();
if(coded == true) {
console.log("Location already geocoded - Submitting form");
$('#searchform').submit();
}
else {
console.log("Location not geocoded - Process location first");
processLocation();
}
});
// CHECK TO SEE IF INPUT HAS CHANGED SINCE BEING GEOCODED
// IF "CODED" VAR IS FALSE THEN WE WILL GEOCODE WHEN SEARCH BUTTON HIT
$("#loc").bind("change paste keyup", function() {
var coded = false;
console.log("Content changed - Coordinates no longer valid");
});
};
// GEOCODE THE LOCATION
function processLocation(){
var geocoder = new google.maps.Geocoder();
var address = document.getElementById('loc').value;
geocoder.geocode({
'address': address
},
// RESULTS - STORE COORDINATES IN FIELDS OR ERROR IF NOT SUCCESSFUL
function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
var coded = true;
console.log("Geocode Successful");
$('#lat').val(results[0].geometry.location.lat());
$('#lng').val(results[0].geometry.location.lng());
} else {
var coded = false;
console.log("Geocode unsuccessful");
alert("Sorry - We couldn't find this location. Please try an alternative")
}
});
}
You are using too many local variables coded. YOu should define one global flag and then can use set it.
When you so
function geocode() {
....
var coded = false;
.....
}
function processLocation(){
....
var coded = true;
....
}
These are not the same coded. To access it globally. You could do somethink like
var coded = false; //initialize with a proper default value function geocode() {
function geocode() {
....
coded = false;
.....
}
function processLocation(){
....
coded = true;
....
}
Note that I'm putting coded outside of all the functions and access it without the var. You should read up on this topic .

Categories