Geolocation

Let's update Kilo to save the location when entries are created. Once we have that information, we'll add a Map Location button that will open the built-in Maps application and drop a pin at the point where the entry was created.

The first step is to add latitude and longitude columns to the database to store the information. To do so, replace the CREATE TABLE statement in ~/Desktop/KiloGap/assets/ www/kilo.js with the following:

db.transaction(

function(transaction) { transaction.executeSql(

'CREATE TABLE IF NOT EXISTS entries ' + ' (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, ' + ' date DATE NOT NULL, food TEXT NOT NULL, ' + ' calories INTEGER NOT NULL, ' + ' longitude TEXT NOT NULL, latitude TEXT NOT NULL);'

Next, we'll rewrite the createEntry() function that we first saw in Chapter 5 to use the geolocation feature of the phone to determine the current latitude and longitude. Replace the existing createEntry() function in kilo.js with this:

function createEntry() {

navigator.geolocation.getCurrentPosition( function(position){

var latitude = position.coords.latitude; var longitude = position.coords.longitude; insertEntry(latitude, longitude);©

return false;

O Begin the createEntry() function.

© Call the getCurrentPosition() function of the geolocation object and pass it two callback functions: one for success and one for errors.

© This is the beginning of the success callback. Notice that it accepts a single parameter (i.e., position).

O These two lines grab the latitude and longitude coordinates out of the position object.

© Pass the latitude and longitude coordinates into a function called insertEntry(), which we'll look at momentarily.

© This is the beginning of the error callback.

O Because we're in the error callback, this will only be called if geolocation fails (for example, if the user does not allow the application to access his location when prompted), so call the insertEntry() function without parameters.

© Return false to prevent the default navigation behavior of clicking the form's Submit button.

Wondering where the SQL INSERT statement got to? Let's take a look at the insertEntry() function. This new function creates the entry in the database. Add the following to kilo.js:

function insertEntry(latitude, longitude) { var date = sessionStorage.currentDate; var calories = $('#calories').val(); var food = $('#food').val(); db.transaction(

function(transaction) {

transaction.executeSql(©

'INSERT INTO entries (date, calories, food, latitude, longitude) ' +

'VALUES (?, ?, ?, ?, ?);', [date, calories, food, latitude, longitude],©

refreshEntries();

checkBudget();

}, errorHandler©

O The beginning of the insertEntry() function, allowing for latitude and longitude values to be passed in. Although there is no way to explicitly mark a parameter as optional in JavaScript, they will simply be undefined if they are not passed in.

© Get the currentDate out of sessionStorage. Remember that its value will be set when the user taps an item on the Dates panel to navigate to the Date panel. When he taps the + button to reveal the New Entry panel, this value will still be set to the currently selected Date panel item.

© Get the calories value out of the createEntry form.

O Get the food value out of the createEntry form.

© Begin a database transaction.

© Pass a callback function into the transaction, with the transaction object as its sole parameter.

O Call the executeSql() method of the transaction object.

© Define the SQL prepared statement with question marks as data placeholders.

© Pass an array of values for the placeholders. If latitude and longitude are not passed into the insertEntry() function, they will be undefined.

© Define the success callback function.

© Define the error callback function.

To confirm that Kilo is actually saving these location values, we'll want to display them somewhere in the interface. Let's add an Inspect Entry panel to display the stored values. We'll include a Map Location button on the panel that will display where the entry was created. Add the following to index.html, right before the closing body tag (</body>):

<div id="inspectEntry"> <div class="toolbar">

<hl>Inspect Entry</hl>

<a class="button cancel" href="#">Cancel</a> </div>

form method="post"> <ul class="rounded">

<li><input type="text" placeholder="Food" name="food" value="" /></li> <li><input type="tel" placeholder="Calories"

name="calories" value="" /></li> <li><input type="submit" value="Save Changes" /></li> </ul>

<ul class="rounded">

<li><input type="text" name="latitude" value="" /></li> <li><input type="text" name="longitude" value="" /></li> <li><p class="whiteButton" id="mapLocation">Map Location</p></li> </ul> </form> </div>

This should look very similar to the New Entry panel that we first saw in Example 4-5, "The HTML for the New Entry panel," so I'll just call out a couple of things:

O The input type has been set to tel to call the telephone keyboard when cursor is placed in the field. This is a bit of a hack, but I think it's worth it, because that keyboard is much more appropriate for a numeric data field.

© The latitude and longitude fields are editable and contained within the form, which means the user would be able to edit them. This probably would not make sense in the final application, but it makes it a lot easier to test during development because you can enter location values manually to test the Map Location button.

© This Map Location button won't do anything when clicked at this point; we'll add a click hander to it momentarily.

Now we need to give the user a way to navigate to this Inspect Entry panel, so we'll modify the behavior of the Date panel such that when the user taps an entry in the list, the Inspect Entry panel will slide up from the bottom of the screen.

The first step is to wire up the click event handler (which we'll create next), and also modify the way clicks on the Delete button are processed. Add the three highlighted changes below to the refreshEntries() function in kilo.js:

function refreshEntries() {

var currentDate = sessionStorage.currentDate; $('#date h1').text(currentDate); $('#date ul li:gt(0)').remove(); db.transaction(

function(transaction) { transaction.executeSql(

'SELECT * FROM entries WHERE date = ? ORDER BY food;', [currentDate], function (transaction, result) {

for (var i=0; i < result.rows.length; i++) { var row = result.rows.item(i); var newEntryRow = $('#entryTemplate').clone(); newEntryRow.removeAttr('id'); newEntryRow.removeAttr('style'); newEntryRow.data('entryId', row.id); newEntryRow.appendTo('#date ul'); newEntryRow.find('.label').text(row.food); newEntryRow.find('.calories').text(row.calories); newEntryRow.find('.delete').click(function(e){

var clickedEntryId = clickedEntry.data('entryId');

deleteEntryById(clickedEntryId);

clickedEntry.slideUp();

e.stopPropagation();©

newEntryRow.click(entryClickHandler);

}, errorHandler

O We have to add the e parameter (the event) to the function call in order to have access to the stopPropagation() method of the event, used shortly. If we didn't add the e parameter, e.stopPropagation() would be undefined.

© The e.stopPropagation(); added to the Delete button click handler tells the browser not to let the click event bubble up the DOM to parent elements. This is important because we've now added a click handler to the row itself (and the entry row is the parent of the Delete button). If we didn't call stopPropagation(), both the Delete button handler and the entryClickHandler would fire when the user tapped the Delete button.

© The newEntryRow.click(entryClickHandler); tells the browser to call the entryClick Handler function when the entry is tapped.

Now let's add the entryClickHandler() function to kilo.js:

function entryClickHandler(e){

sessionStorage.entryId = $(this).data('entryId'); db.transaction(

function(transaction) { transaction.executeSql(

'SELECT * FROM entries WHERE id = ?;', © [sessionStorage.entryId], function (transaction, result) { var row = result.rows.item(0); var food = row.food; var calories = row.calories; var latitude = row.latitude; var longitude = row.longitude; $('#inspectEntry input[name="food"]').val(food); $('#inspectEntry input[name="calories"]').val(calories); $('#inspectEntry input[name="latitude"]').val(latitude); $('#inspectEntry input[name="longitude"]').val(longitude); $('#mapLocation').click(function(){

window.location = 'http://maps.google.com/maps?z=15&q='+ food+'@'+latitude+','+longitude;

jQT.goTo('#inspectEntry', 'slideup');

errorHandler©

O Get the entryId from the entry that the user tapped and store it in session storage. © Begin a database transaction.

© Pass a callback function into the transaction, with the transaction object as its sole parameter.

O Call the executeSql() method of the transaction object.

© Define the SQL prepared statement with a question mark as data placeholder. © Pass a single element array for the placeholder. Q Begin the success callback function.

© Get the first (and only, since we're just querying for one entry) row of the result. © Set some variables based on the values from the row. © Set values of the form fields based on the variables.

© Attach a click handler to the #mapLocation button. The function sets the window location to a standard Google Maps URL. If the Maps application is available, it will launch. Otherwise, the URL will load in a browser. The z value sets the initial zoom level; the string before the @ symbol will be used as the label for the pin that is dropped at the location. The latitude and longitude values must appear in the order shown here, separated by a comma.

© Call the goTo() method of the jQTouch object to make the Inspect Entry panel slide up into view.

© Define the error callback function.

To test your changes, open a command prompt, cd into the KiloGap directory, and run the following commands to recompile and install the app on your phone:

ant debug adb -d install -r ~/Desktop/KiloGap/bin/Kilo-debug.apk

0 0

Post a comment