3

I have two PHP functions - one that builds a html form and another that handles it when it's submitted.

I want to add to the html a javascript onClick function that sends one of the form's fields to an external API (Google Maps) and saves the reply in a hidden field in the form so that the handling PHP function will get that data as well.

My question is - how do I make sure the handling PHP function only fires after the onClick function has finished?

Or maybe I can't and I have to use ajax?

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
ash
  • 33
  • 1
  • 4
  • possible duplicate of [Best way to Integrate a Javascript result with PHP](http://stackoverflow.com/questions/3611975/best-way-to-integrate-a-javascript-result-with-php) – Pekka May 06 '11 at 15:47
  • @Pekka: Similar, but hardly an "exact duplicate". – Lightness Races in Orbit May 06 '11 at 15:55
  • @Tomalak my reasoning is that this question pops up in hundreds of different variations almost every day, and the answer always is the same boring "it's impossible, use AJAX". We need some sort of reference question to close these as a duplicate of. If we can't find a "pure" one, we may need to start one – Pekka May 06 '11 at 15:56
  • @Pekka: "There are other questions with the same answer" is not the same as "this is _exactly_ the same as another question". – Lightness Races in Orbit May 06 '11 at 16:00
  • @Tomalak The question is always fundamentally the same: "How can I parse a JavaScript result in PHP?" I see no value in having 1,000 questions with the same answer. These need to be closed as dupes. – Pekka May 06 '11 at 16:02
  • @Pekka - i agree. a feature for marking things as duplicates would be great. Maybe something where answerers can link in the duplicate question and it get approved by some kind of popular vote (so it's agreed that the question is duplicate and the approved answer fits in both cases) – Jim Rubenstein May 06 '11 at 16:02
  • 1
    I think there's some confusion about the question here. The wording OP's question does make it sound like they're blurring the line between server and client-side code, but their proposed strategy of using JavaScript to query the API (of course, AJAX would come into play here) and store the result in a hidden form field is on the right track. It sounds like the real question is: "How do I make sure my JavaScript finishes running before the form is submitted?" – John Flatness May 06 '11 at 16:05
  • @Pekka: I see no value in spending your entire life tracking them all down, and that's what you'd have to do. – Lightness Races in Orbit May 06 '11 at 16:08
  • @Jim: Marking questions as duplicate already exists, you just need enough reputation ;) – Felix Kling May 06 '11 at 16:08
  • @Tomalak that's not the point. The point is to be able to close *future* questions as a duplicate, instead of answering them over and over and over (unless the question has enough code to warrant a specific answer - although admittedly, @Jim's looks *very* good) – Pekka May 06 '11 at 16:09
  • @Felix Kling - I guess I just haven't reached the required level of elitism yet! – Jim Rubenstein May 06 '11 at 16:12
  • @Tomalak what's your suggestion then? Work towards 3 million questions, 2 million of which are duplicates? That sounds much more depressing to me than doing a bit of closevoting every day. – Pekka May 06 '11 at 16:14
  • @Jim: http://stackoverflow.com/privileges/close-questions ;) – Felix Kling May 06 '11 at 16:15
  • @Pekka: Then, as hoped, you'll have fun with that! – Lightness Races in Orbit May 06 '11 at 16:23

4 Answers4

4

You'll need 2 events to accomplish this.

  1. the onClick event for your button that executes the google map request and saves the data into the local form
  2. an onSubmit event for your form. You will use this event to see if the form is submittable. Basically, check to make sure that your google map request has been run and has completed before allowing the form to submit.

Example:

<script>
var googleMapsDone = false;
$('#gmap_button').click(function(event)
{
    doGoogleMapThing(function()//callback from googlemaps
    {
        //save data to local form for processing by script once posted
        googleMapsDone = true;
    });
});

$('#form').submit(function(event)
{
    //check to see if we did our google maps stuff, and that its done
    if (false == googleMapsDone)
    {
        event.preventDefault();
        return false;
    }
});
</script>

With that code, any time the user is waiting for google maps and clicks submit, nothing will happen. They would have to wait on the response from GMaps, THEN click submit. This is okay for some things, but if you're trying to do background requests to GMaps that require no user input/interaction/feedback (maybe getting Long/Lat of their address when they submit a form), then you can modify the code a bit to post when you get the response. An example of that would be:

<script>
var googleMapsDone = false, submitWaiting = false;
$('#gmap_button').click(function(event)
{
    doGoogleMapThing(function()//callback from googlemaps
    {
        //save data to local form for processing by script once posted
        googleMapsDone = true;

        /* check to see if submitWaiting is TRUE.  If it is, automatically
           post the form when we get the response.
        */
        if (submitWaiting)
        {
            $('#form').submit();
        }
    });
});

$('#form').submit(function(event)
{
    //check to see if we did our google maps stuff, and that its done
    if (false == googleMapsDone)
    {
        event.preventDefault();

        /* set our submitWaiting flag which we will use in our clalback
           so when we get our google maps response, we post our form right
           away
        */
        submitWaiting = true;

        /* You might want to display a modal or some other kind of notification
           that the form post is 'working' or 'processing' so when the user
           clicks it and doesn't see anything happening, they don't bail
           or click it 800 times out of frustration
        */

        return false;
    }
});
</script>

edit: I realize my comment below on how this works are...hard to understand, so let me explain here, then show an alternative.

  1. User fills out form
  2. User clicks button to do stuff on google maps (example was written before I knew the scope/context of the GMaps request, so that's why it's done this way)
  3. If user then clicks 'submit' before the GMap request is complete, we CANCEL the submit and set a flag submitWaiting
  4. GMaps request returns, and executes our callback. Our callback knows how to look for submitWaiting and if it is set to true it submits the form

An alternative to this, instead of requiring user interaction for the GMaps request you could change the event to an onChange event for the input box of the address, or you can do it all via the submit button/event, like so:

<script>    
$('#form').submit(function(event)
{
    //lets look up our user's address!
    doGoogleMapThing(function()//callback from googlemaps
    {
        //do stuff with your inputs, or whatever

        $('#form').submit();
    });

    event.preventDefault();
    return false;
});
</script>

edit note: the examples above assumes you're using jquery, and that your google map API request is done via javascript

If your google map api request is not using the google maps javascript library, this is STILL possible, just requires you to make a "proxy" script to the API via a php script on your local domain. (Browser restrictions). It'd be something like THIS:

<script>
function doGoogleMapThing(callback_when_done)
{
    $.post("/path/to/proxy/script.php", { data: to, post: to_server }, function(response)
    {
        //check & parse response
        callback_when_done(/* Data needed to populate form */);
    });
}
</script>

note: both of these examples assume jquery usage. because...well..why wouldn't you.

Below is an implementation of your exact script. I changed it a bit to use jquery, because it makes things a bit less painful.

<script type=”text/javascript” 
    src=”http://maps.google.com/maps/api/js?sensor=false”></script>
<script type="text/javascript" 
    src="http://code.jquery.com/jquery-1.6.min.js"></script>

<script type=”text/javascript”>    
function codeAddress(callback)
{
    var address = $('#address').val();

    geocoder.geocode( { 'address': address}, function(results, status)
    {
        if (status == google.maps.GeocoderStatus.OK)
        {
             $('#latitude').val( results[0].geometry.location.latitude );
             $('#longitude').val( results[0].geometry.location.longitude );

             if (typeof callback != 'undefined') //call the callback.
             {
                 callback(true);
             }
        }
    });

    /* Just in case google returns a bad response or doesn't return at all
       we need to add a timeout that will call the callback after 10 seconds
       just so we make sure our user doesn't hang.
    */
    setTimeout(function(){
        callback(false); //pass false indicating no/invalid response
    }, 10000); //10000ms = 10s
}


/* We are using the reallySubmit variable as a flag here, to know when we finish
   our call to google maps and that we want to really submit our form.

   we have to do this because the form.submit() call fires the form's submit event
   again, and we end up going into an infinite loop.

   an alternative to this would be to bind your form processing to the form's submit
   button's click event. that should also pick up any presses of the enter key, also.
   the solution below also works.
*/
var reallySubmit = false;
$('#form').submit(function(event)
{
    if (false == reallySubmit)
    {
        //lets look up our user's address!
        codeAddress(function(success)//callback from googlemaps
        {
            reallySubmit = true;  
            $('#form').submit();
        });

        event.preventDefault();
        return false;
    }
});
</script>
Jim Rubenstein
  • 6,836
  • 4
  • 36
  • 54
  • Thanks, Jim. But it looks to me like there's something missing in the submit function. What if the GoogleMaps stuff takes longer than it takes the onSubmit function to fire? Then the submit will always fail. Shouldn't there be some sort of listener or maybe a loop in the submit function that waits for Goggle to finish and only then validates the form? – ash May 08 '11 at 11:35
  • If that's the behavior you wanted, you could absolutely do that. You don't need a loop or a timer to accomplish it though, I'll edit my answer to show you an example. – Jim Rubenstein May 08 '11 at 12:12
  • @Jim - thanks a lot for the update. You're a mind reader. I want the javascript to take the address the user entered, send it to GMaps and get the lat/long as a reply, put them in hidden fields and only then submit the form for the PHP to handle. But I still can't see how you handle the waiting. It looks to me like both functions are fired when the submit button is clicked and only fired once. It still looks like the submit wont happen, but the submitWaiting flag would change to true. Aren't we stuck again then? – ash May 08 '11 at 12:41
  • @Ash - there is an event attached to a button (#gmap_button) in my example code. In my example, I assume the user clicks something (other than submit) to get their information. You can attach this to a `change` event on the address input, or even to the `submit` event on the form if you want. But in my example what happens is: User clicks button to do gmap stuff. if they click submit, the submit sees gmap isn't done, doesn't submit the form, but sets a flag that the gmap function callback knows to look for, and if present, submits the form when the gmap call is complete. does that make sense? – Jim Rubenstein May 08 '11 at 13:02
  • @Jim - Yes, I need it all to happen when the user clicks the submit button. So your latest example is most suitable (and seems most simple). If I understand correctly $('#form').submit() will occur only after the googleMaps stuff has finished. If so, than now we have the problem you described earlier - what if the Google stuff takes too long? I want to wait a max of 10 seconds and then submit without the Google's stuff done. Is it possible? – ash May 08 '11 at 13:55
  • Sure, in the submit button event, you can do `setTimeout(function() { $('#form').submit(); }, 10000); //10,000 ms = 10 seconds` - so, basically, whichever one finishes first, submits the form. – Jim Rubenstein May 08 '11 at 15:02
  • @Jim, I'm having a hard time trying to combine the JavaScript which 'talks' with google maps together with your script (the third one. It's the one where we do everything under the submit function). I would highly appreciate it if you could help me with that as well. I've placed my script here - [link](http://www.mediacoverages.com/script/) – ash May 11 '11 at 11:12
  • @Ash, I looked at your script, and edited the end of my answer to have an implementation that you can use with it. I didn't have to change too much, but I did include jquery and use that because it makes life significantly simpler. – Jim Rubenstein May 11 '11 at 13:09
  • @Jim, it looks great and makes sense. I'm just not sure about the parts with the words 'callback' and 'success'. Am I supposed to replace them with anything or leave them as they are? Also, the submit part shouldn't be conditioned by the success of the google stuff. If you remember, if google doesn't return a valid answer after ten seconds, the form should submit anyway (maybe with lat/long default values of 0?) – ash May 11 '11 at 16:30
  • The `callback` is a closure (anonymous function) in javascript. The request to Google is asynchronous, which means javascript code execution does not stop and wait for a response from google, because of this, you can't just "wait for the function to return." To achieve the result we want, we pass a callback (closure) to the function that does the work with google, and when google replies to us - we execute the callback (which submits the form). I edited the code to allow for a failed/un-returned response, also. – Jim Rubenstein May 11 '11 at 17:43
  • @Jim, if there's still some wind left in you for listening to my problems they're still not over. The js script is a part of a html form which is built by a PHP function with this syntax: $form .= apostropheapostrophe ; . The problem starts when the script includes an apostrophe of its own, then I get a parse error. How can this be solved? – ash May 11 '11 at 20:59
  • @Jim, thanks for your patience. The parse error problem is solved. But it seems the submit function in the script never fires. I know this because I've put alerts through out the script and only the one outside the two functions fires. I've put [here](http://www.mediacoverages.com/phpscript/) the entire php function which builds the html form, including your script (yours and my code in yellow). Maybe there's a clue in the submit button definition (in red). I would appreciate it a lot if you could try to figure out why the submit function doesn't fire. – ash May 12 '11 at 11:42
  • Where my code has `#form` you need to replace that with `#simplr-reg` – Jim Rubenstein May 12 '11 at 17:51
  • @Jim, almost there... Both functions run now and google returns the lat/long. But there's a loop between the two functions. I've placed the script [here](http://www.mediacoverages.com/script2/). I've scattered alerts through out the script and the order they appear in after submitting is - 2, 3, 4, 5, 6 and then 2, 3, 7, 4, 5, 6 in an endless loop. Can you please say what's causing the loop? – ash May 12 '11 at 23:58
  • Sigh. I believe the problem is that we're attaching the logic to the form's `submit` event, and then later calling `form.submit()` which causes the submit event to be fired again. this never used to be a problem for me and only started to happen to me over the last few days. easiest way to fix this is.../me edits code above – Jim Rubenstein May 13 '11 at 12:49
  • @Jim, We got rid of the loop and the run goes through all the alerts but in the end the form doesn't submit. BTW, if you say the loop problem has started recently maybe it's related to the jquery's version... – ash May 13 '11 at 13:34
  • @ash make sure you're getting to the line that executes the submit, and make sure you added the bit that changes the flag to tell the submit event it's okay to submit. if that doesn't work, return true at the bottom of the submit event closure...if that doesn't work...then, i think something else might be going on – Jim Rubenstein May 13 '11 at 19:59
  • @Jim, it took me a while but I found out the point where the form doesn't submit though it should. I just don't know how to solve it. The problem is that after the inner call to the submit function the function does start but when it ends the form doesn't submit. I've stripped the script down to only include the necessary stuff to demonstarte that. If you can take a look it's [here](http://www.mediacoverages.com/simplescript/). Returning true didn't make a difference. – ash May 16 '11 at 15:12
2

This is fundamentally impossible - PHP runs on the server, JavaScript runs on the client. You will indeed need to use Ajax.

Pekka
  • 442,112
  • 142
  • 972
  • 1,088
  • You could also create a hidden `iframe` to send a request. Bad though, but it would work! – James May 06 '11 at 15:50
  • 1
    And more to the point, the PHP has finished running and the context has gone out of existence _long_ before any Javascript engine ever gets a look in. – Lightness Races in Orbit May 06 '11 at 15:54
  • Not fundamentally impossible, fundamentally mis-understood. – Jim Rubenstein May 06 '11 at 15:56
  • @Tomalak and @Jim - I understand that the PHP that builds the html finishes running before the javascript runs, but this is the php function that builds the html you're talking about. I also mentioned in my question there's another php function - one that handles the submitted form. I need this one to run only after the javascript finishes its job. – ash May 08 '11 at 11:40
0

AJAX is a must I am afraid. You need to redirect the user to a page with the script.

Once the server has run the PHP to generate the HTML, then sent it to the browser, there is no way to run it again, or indeed run anything from the server, without making a request to the server. That is the nature of a server side scripting language, of which PHP is.

Mild Fuzz
  • 29,463
  • 31
  • 100
  • 148
  • Although, I will add that is no where near as scary as it sounds. Just dive in, I am sure you'll swim!! – Mild Fuzz May 06 '11 at 15:49
  • thanks. But - so how come there's a function that handles the submitted form? Surely this one runs after the form has been built (and submitted). – ash May 08 '11 at 11:43
  • yes it does, you can submit the form to the current page, and filter the php function to only run if the form has been submitted (if(isset($_POST['your_form_field'])...) – Mild Fuzz May 09 '11 at 08:47
0

You can have a hidden IFRAME element where you load a php document as soon as your javascript ends. Simple, but effective (as long as your php document comes from the same URL).