At lunch today I took a quick look at how Safari on the iPhone handles Geolocation. If you have an iPhone (you know, that device that doesn't play Flash?) then you've probably seen applications that ask permission to locate your position. The hardware supports it both for applications but web pages as well. I was curious to see a) how difficult it would be to use and b) if it could degrade nicely for other clients.
So to begin, I found an excellent blog post that demonstrated a simple example of the code: How to Use Geolocation in Mobile Safari.
His code sample was simple and to the point:
function foundLocation(position)
{
var lat = position.coords.latitude;
var long = position.coords.longitude;
alert('Found location: ' + lat + ', ' + long);
}
function noLocation()
{
alert('Could not find location');
}
navigator.geolocation.getCurrentPosition(foundLocation, noLocation);
Basically - run a getCurrentPosition and pass it a pass/fail operator. I tested this just to ensure it worked and it did perfectly. Using that as a base I began to work on a simple application.
My idea was simple - I'd tie onto Yahoo's Local Search API. I'd have a form where you could enter "pizza", and because we knew your longitude/latitude, we could pass that to Yahoo. To support browsers/clients that couldn't use this, I'd use jQuery to simply ask the user for their location. This works because Yahoo's APIs supports both a precise location, or a precise street/city/state, as well as a loose "location" argument. Kudos to Yahoo for being so flexible.
Before I go further - one thing I don't like about JavaScript based blog entries is that it's sometimes difficult to explain the process. The code I'm going to show was done over multiple iterations, but since I didn't save each and every edit, your only going to see the final result. Do know that I certainly didn't write it out like this - it took more than one try to get it right.
That being said - let's begin with the view area:
<div id="results"></div>
<form id="searchForm">
Search for: <input type="text" id="term" value="pizza">
</form>
<input type="button" id="searchBtn" value="Search" disabled="true">
Nothing too fancy here. You can see a form with a search field, a button (I'll explain why I put it outside the form in a PS), and a div for my results. Now let's go up to my JavaScript.
function foundLocation(position) {
lat = position.coords.latitude
long = position.coords.longitude
$("#searchBtn").attr("disabled", false)
$("#results").html("")
} function noLocation() {
$("#results").html("")
$("#searchBtn").attr("disabled", false)
$("#searchForm").append(" Location: <input type='text' id='location'>")
} $(document).ready(function() {
if(navigator.geolocation == null) noLocation()
else {
$("#results").html("<i>Locating you...</i>")
navigator.geolocation.getCurrentPosition(foundLocation, noLocation)
} $("#searchBtn").click(function() {
$("#results").html("<i>Searching...</i>")
var theURL = "http://local.yahooapis.com/LocalSearchService/V3/localSearch?callback=?"
var data = {
appid:"5rJLaITV34GaU8t05tz.MIYQfRol9PBL51PyK3KAhmRiFa0FEAtvFyD36r4xTns_VRP1",
output:"json",
query:$("#term").val()
}
if(lat != "" && long != "") {
data.latitude = lat
data.longitude = long
} else {
data.location = $("#location").val()
} $.getJSON(theURL,data, function(d,s) {
var result = ""
for(var i=0; i<d.ResultSet.Result.length;i++) {
var node = d.ResultSet.Result[i]
result += "<p><b>" + node.Title + "</b><br/>"
result += "<a href='" + node.ClickUrl + "'>"+node.Url+"</a><br/>"
result += node.Address + ", "+node.City+", "+node.State+"</p>"
}
$("#results").html(result)
})
})
}) </script>
<script>
var lat = "";
var long = "";
Ok, there's a lot going on here. Let's focus first on the document.ready block. I begin by checking to see if the navigator.geolocation object exists. If it doesn't, it means we are on a browser where we can't get the user's location. That runs the noLocation function. It enables the search button for my form (which I disabled while I was working) and adds a field for the user's location.
If the geolocation object exists, we fire off the request. Now notice - if for some reason the lookup fails, we still run the noLocation function. So we've got support for both non-supported clients and the user saying no to the location request. (Or some other error happening.) If the location works right, we have access to the user's longitude and latitude. We store those values and enable the button. (Notice I made use of the results area to tell the user I was looking up his location.)
Ok, so the final bit of code handles the Yahoo API stuff. I've got a click event listener for the search button. It can tell if it currently knows the long/lat value and will use it if so. Otherwise it uses the freeform location field. I construct my data and fire it off to Yahoo. The result handler simply outputs the result.
You can demo this here: http://www.coldfusionjedi.com/demos/feb1210/test4b.cfm
I tested it in Chrome, my iPhone Simulator (which uses a California location) and my actual iPhone, and it seems to work well.
p.s. So why did I put the button outside of the form? Probably a dumb reason, but I had trouble getting jQuery to add my location field before the search button. When I did a prepend, it just didn't show up. When I append to the search term field, it didn't work. When I appended to the form tag - it worked perfectly - but added it after the search button. Probably something silly I did wrong there - but I went with a quick fix.
Archived Comments
Great example... for the jQuery thing, I think insertAfter() would work:
$("Location: <input type='text' />").insertAfter("#term")
Or insertBefore() the searchButton...
To add support for android - add this at top:
<script type="text/javascript" src="http://code.google.com/apis..."></script>
Then make this change to the javascript:
$(document).ready(function() {
if (window.google && google.gears){
$("#results").html("<i>Locating you...</i>")
gps = google.gears.factory.create('beta.geolocation');
gps.getCurrentPosition(function (position){
foundLocation(position);
})
}
else {
if (navigator.geolocation == null)
noLocation()
else {
$("#results").html("<i>Locating you...</i>")
navigator.geolocation.getCurrentPosition(foundLocation, noLocation)
}
}
I sent Ray the file if the comments don't handle this.
^Scott P - hmm, interesting, doesn't look like Ray's added that code yet but on my Android it still ask's if I want to allow my location to be used, how's that working then? (No results are returned, but then even if I try it in a browser and enter my UK lat&long I get NY pizza results, so I'm guessing something else may be up)
@Kit: I'll try that next time. I knew there was a way.
@ScottP: Interesting. Query - why "window.google" and "google.gears"? Could you shorten it to window.google.gears != null or some such?
Just did a quick Google and found these jQuery plugins...
http://code.google.com/p/jq...
http://github.com/fabiant7t...
...both of which seem to also work fine on my Android without any specific google code being called or tested for, so looks like navigator.geolocation is compatible with both iPhone & Android (anyone confirm?)
Is there an Android simulator?
Btw - dumb question - but "Android" is a generic term, right? For a bunch of phones? Like the new Nexus One?
@adam - no clue, mine didn't work until I added that. I'm on stock droid eris.
@ray http://developer.android.co...
ray - the sdk is here:
http://developer.android.co...
Android is the operating system, several phones run it.
http://en.wikipedia.org/wik...
@Ray - as Scott linked to, grab the SDK (which uses Eclipse so you should feel fairly at home) and the emulator comes with it. And yeah Android is Googles mobile OS, so Nexus 1, bunch of recent HTC phones, upcoming Dell Mini 5 etc... many more to come this year.
@Scott - I'm on a G2 with a hacked 2.1 rom, so maybe it's new in the latest updates?
ahh, just noticed on the wikipedia page, 2.1 brought 'New browser UI and HTML5 support' ... that may be it.
(sorry to hijack your iPhone thread Ray :)
yeah - iPhone is dead platform.
Try using jQuery's before() and after() instead of append() or prepend() so you can have your button inside of the form.
If one uses this type of thing then it is usually nice to allow the user a chance to correct the location.
My iPod Touch seems to claim that it is in Washington DC when I am actually on the west coast.
This was very helpful. Exactly what I needed.
@David: Yeah, good point.
That is very cool.
Hi Ray,
Maybe I am missing something here, but, with I assume developers aiming for compatibility across all OS's don't most mobile devices support ECMAScript X. Is this above, with the additional comments, intended because these mobile devices don't support the ECMAScript standard, i.e., javascript Location Object. Thanks
Henry
I'm afraid I don't get what you are saying. Do you have a URL for the JS Location object? I'm only aware of document.location.
Sorry about that. Don't all(or most current) mobile devices, that are equipped with Browsers, support javascript, therefore, the location can be determined through javascript's Location object and the device's hardware?
Thanks
What Location object though? That's the question I'm asking. I know of document.location, which is the URL object, but what do you mean? A URL reference would be handy.
Hi Ray, Banging my head on a table.....
In my hack of this script I am trying to send the
data.latitude = lat
data.longitude = long
to a cfc to set client.latitude in order to locate a user on a map as per Adam Tuttles solution for setting client variables on http://stackoverflow.com/qu...
Please please with candy on top can you help me figure out how to send the location data to my ClientFacade.cfc so I can pass the location data around my app! Thanks in advance...
Once you have the long/lat, you simply make the Ajax request. Look at his setValue.click function. Notice the $.getJSON function. That performs the Ajax request. Just use that portion.
ack. still trying. the cfc setting is working... I just can't seem to see how to write the json my code is (using google gears for development purposes here - my iphone 3g is definitely not that quick loading (I want a Droid... :) ):
var geo = google.gears.factory.create('beta.geolocation');
var latitude = {"mylatobject": function updatePosition(position) {
(position.latitude);
}
};
$.getJSON(
'clientFacade.cfc',
{
method:"set",
returnFormat:"json",
name:"foo",
"val":valu,
"mylat":latitude
},
basically my results are always 'undefined'... :(
Can you show us the entire code - or point us to the url?
The blog keeps flagging me as spam when I try to paste the code so I replied to your comments notification email address
http://www.environmental.ly...
Wow - your code is seriously messed up. :) I see the JS, then I see a copy of it outside of a script block (well there is a closing script block). Did you mispaste something? Also, I never saw my Long/Lat. Job one is to ensure that part works -then look into making it set and getting get to work.