Welcome to the fourth part of my blog series on building a Parse.com enabled PhoneGap application. If you haven't yet read the earlier entries in this series, please see the links at the bottom for background and the story so far. In today's entry we're going to add geolocation to the application. This will be supported both in the tip reporting mechanism as well as in the page that fetches tips.
Luckily, Parse.com makes building location-aware applications very easy. In fact the code for these features are so simple you can add them within five minutes. Unfortunately, what isn't so easy is deciding how to use these features. I'm finding that more and more it isn't so much a problem of "How do I do X" but more "How do I X in a way that best works for my users." Consider the simple fact that geolocation can - and does - fail. What do I do? Do I let users enter an address? Do I display a small map so they can touch to select their location? What if they lie? Or what if they are just wrong?
At the end of the day - I don't think I'm a UX (User Experience) expert. Like most folks I recognize good UX versus bad UX, but it is much harder to decide what the best choice is when building your own apps. Hopefully a year or two from now I can say, confidently, that I'm making the right decisions. For now though I'm going to struggle through it (and - of course - share these struggles with you guys).
For now I've decided to make geolocation required. For adding a tip you will be prevented from saving the data if the geolocation check fails. For displaying tips, if we can't get your location, we don't bother getting any of the stored data.
Let's begin by looking at the changes to the tip form page. The first thing I did was add "disabled" as an attribute to the submit button. This means the page loads with the form immediately disabled. Next, I wrote the following snippet that will execute when the Add page loads.
currentLocation is defined in the root of my JavaScript file so it is a global variable. If everything works well, then we simply enable the form button. Otherwise, the user gets an error.
Once we have the user's location, how do we store it? Parse.com supports what are known as GeoPoints. Any Parse.com data type can support a geopoint, but only one geopoint. A geopoint is simply a longitude/latitude data pair. Using it is quite simple though. Consider this code taken from the form submission handler.
Compared to our previous example, there are only two changes. First, I make an actual Geopoint with my location data. Then I simply add this value to the tip object. Remember that I said that we can - if we want - change the structure of our data at any time. After the application is released this is probably a bad idea (and I'll talk about how you can avoid that in the next entry), but for now it is pretty helpful.
That's it for the storage aspect. Any records created with this geopoint can now be searched in new and interesting ways. If you remember, our previous code for the "get" page grabbed everything from the database. This was as simple as creating a query and just running find on it. I wish it were more complex.
Here is where the beauty of Parse.com's Query system comes to play. If we want to filter the results, we can simply add more options to the object. For example, assuming I have your location, it takes one line to find data near you:
query.near("location", myLocation);
Seriously - that's it. In my tests this seemed to be within 50 miles or so. Oddly the docs for "near" do not specify exactly what near is. Near like the Sun? Near like Starbucks? I'm just not sure. However, they do provide a few alternatives. These are much more complex though. Instead of typing near, I have to type "withinMiles":
query.withinMiles("location", myLocation, 30);
Yes - I'm being a bit of an ass - but I can't stress enough how darn cool this is. (In case you don't live in a proper country with Imperial units, they also support withinKilometers.) I decided that for my application I wanted to return tips within 30 miles and reports that were no older than 7 days. Adding the date filter was three lines of code that I could have written as one:
var lastWeek = new Date();
lastWeek.setDate(lastWeek.getDate()-7);
query.greaterThan("createdAt", lastWeek);
Here's the entire logic for retrieving data on the Get page.
You may notice that I've moved the render portion into its own function. For the mapping portion of this application I decided to try the free mapping service Leaflet. Leaflet is not only free, but really easy to use too. Here's the function I use to render out to the map:
Even if you've never seen Leaflet before, you can probably read that easily enough and see what it is doing. For each result I add a marker along with a little info window you can click to get the details. You can see an example of this below:

All in all - I've now got a complete, if basic, application. Don't forget you can see the complete source code for the application at the GitHub Repo and you can download builds of the application on the public PhoneGap Build site.
In the next, and final, part to this series, I'm going to discuss what can, and should, be done both on the PhoneGap side as well as the Parse.com side.
Archived Comments
looking forward to the next post in the series!!
Thanks. I'm a bit disappointed by the amount of comments on this series - but - heck - its been fun to write. ;)
this series is awesome and really helping me to get started with parse.. I was initially a bit scared of parse, because not familar with backbone, but your example shows a simple enough example to get me started.. what about notification services.. that would be cool to see you hooking into..
I really do want to do notifications - I'm just not sure how I'll get it into the app. The obvious way would be for the user to say, "Yes, I want notifications", and they get a notice when some reports a tip. Which... kinda makes sense. I'll try to do that this week.
Really happy you're liking it Sam!
It's a good series Ray, probably one of the most useful things I've read recently. It's a great tutorial, but I suppose was waiting for the final post before posting a comment!
Are you going to go through and do app store submissions?
Pete: I was considering it. Honestly, I don't know if Apple will accept it. Also, as much as I want this to be a "real" app, at the end of the day, the main point is to educate. So for example, notifications I think would be incredible. I may add them in a silly way just to get to a point where I can demo it. I'd rather do that even if it means i can't really release it as an app. Does that make sense?
Anyway - right now my thoughts are:
1) Ignore my last comment above and do Notifications
2) Then do the final entry talking about 'final' stuff for PG and Parse.
I kinda wanted to do Facebook connect as well, but I may just do that in a post by itself.
It doesn't matter if they reject it, it'd be a good end to the guide to show people how to submit. Getting the right sized screen shots etc, the difference between Google and Apple.
And Facebook connect would be good to see, I'm struggling with that in a PG app at the moment.
My problem with that though is that the submission process is pretty big, and I'd say a bit OT for the main thrust of this series. Plus, there are guides already out on this.
But - I could look into it - and possibly even recommend good guides for this.
Let me consider it for a post series followup. :)
I looked into Notifications today and wow - it was a bit more difficult than I thought. I'm going to blog it tomorrow, but as a separate blog entry, not part of this series. That distinction probably isn't that important. ;)
Thanks for the tutorials - trying to figure out what BaaS to use and this is helping push me towards parse. Any suggestions on how to set up offline use and being able to store data offline and check for updated content?
Is this what you mean?
http://www.raymondcamden.co...
Thanks - Just came across that as well. It is a close, but I would like to keep the full set of data on the device and only retrieve the content if it has been changed.
Then you need some way to note a change. Perhaps your server could use a table that just records a 'lastmod' value. Whatever the way you do it, you would have your mobile app ask the server to see if it is fresher than now, and then do the updates.
Does that make sense?
Yep, that makes sense, thanks. I will see if I can get something simple working.
Great series and a got a standalone page with all the Parse and map functionality working perfectly. But when I try and integrate it into my PG,JQM app the map doesn't fire. I have moved all the library Parse/Leaflet js/css files to the home page and visible in the resource tab loaded. Left the custom js file getting the data which is ok in the console. Googling suggests JQM and DOM but that's beyond me at the moment. Any suggestions? Thanks
Well, it could be anything really. If you are testing Android, can you try console debugging via DDMS? (See my other blog post on that.)
Great series of tutorials - many thanks
I downloaded the code from GitHub.
I re-built the app on Phonegap Build. The only change I made was to change 'phonegap-version' to version 3 in config.xml (phonegap wont build it with version 1 any more). and to put the reference to phonegap.js in index.html.
I can install the app on my Android tablet however it doesn't work. If I click on 'Add tip' I get the form however when I click on 'send tip' nothing happens. Also when I click on get tips I don't get the map.
It's working fine for me locally in the browser, both within Ripple and outside it. Also the app works fine on my Android when installed directly from Google Play.
Any ideas?
So to be clear, it works via Play, but not when you use PG Build?
Yes that's right
Google Play (the app you put on Google Play) - works
Locally in Ripple / Chrome - works
PG Build (re-built as per my last message) - doesn't work
Not sure what it could be then. If it works when you build locally but no in PGBuild, you may need to open a support request with them.