Build a Location-Based Mobile App With HTML5 & Javascript: 5
In the previous part of our series, I explained how to use the Google Maps API to display stored locations on a map and to trace a route from the user’s current position to his car. I also demonstrated several utility functions that will be useful during the development of the application. In this penultimate article, I’ll describe the final utility function and those that complete “Where I parked my car”. In the final part of the series, we’ll demonstrate how to run our finished Cordova application optimally.
Utility Functions
As described in the introduction, there is still one more utility function to explain, and that is the one to retrieve the value of a parameter inside a URL.
Retrieving a URL Parameter’s Value
The HTTP protocol has several request methods, but the two most common are GET and POST. As you most likely know, the GET method sends the data through the URL appending variables and values after a question mark (www.website.com?variableone=build&variable2=mobile). Unfortunately, jQuery Mobile doesn’t offer a native method to retrieve a single parameter of the URL. In fact, the only method that does something similar is $.mobile.path.parseUrl()
. However, this method returns an object having a property called search
that gives you all the parameters, not just the one desired.
To meet our development needs, I created the following function.
function urlParam(name)
{
var results = new RegExp('[?&]' + name + '=([^&#]*)').exec(window.location.href);
if (results != null && typeof results[1] !== 'undefined')
return results[1];
else
return null;
}
Initializing the Application
Now that you’ve seen all of the utility functions, I’ll explain the two initialization functions that power “Where I parked my car”. The first function we’ll look at, called initApplication()
, determines the behavior of application elements like homepage buttons (index.html
). The positions’ history page (positions.html
). initApplication()
is responsible for managing several other elements, which you’ll see in greater detail in the following sections.
Disable Buttons Conditionally
In part 4 of our series, I illustrated the requirements of our application, showing you the function that tests to see if they all are satisfied (checkRequirements()
). When the user runs “Where I parked my car”, if the test fails, the buttons to set the position and to find the car will be disabled, and the user will be notified of the connectivity issues. (In this scenario, they lack the Internet access needed to use the Google Maps API.) This is done using the following code:
$('#set-car-position, #find-car').click(function() {
if (checkRequirements() === false)
{
$(this).removeClass('ui-btn-active');
return false;
}
});
Be Responsive
To enhance the application interface and make it responsive, I created the function updateIcons()
that I’ll use as a handler for the pagebeforecreate
and the orientationchange
events.
$(document).on('pagebeforecreate orientationchange', updateIcons);
Get or Set the Position
Th next piece of code, one of the most important parts of the whole app, is the callback function attached to the pageshow
event of the map-page
element. It’s responsible for checking if the page has been required to set or to get the car’s position and act accordingly using the urlParam()
function. If the value of the variable requestType
is set
, we’ll use the getCurrentPosition()
method of the Geolocation API to retrieve the position and then store it in the app database using the Web Storage API. After saving the position, I’ll use both requestLocation()
and displayMap()
methods of our Map
class to retrieve the address of the car’s position and to display the map indicating the user position with a marker.
On the other hand, if the value of requestType
is get
, we’ll use the watchPosition()
method of the Geolocation API to poll the user location to constantly refresh the route to his car.
The necessary code is below.
$('#map-page').live(
'pageshow',
function()
{
var requestType = urlParam('requestType');
var positionIndex = urlParam('index');
var geolocationOptions = {
timeout: 15 * 1000, // 15 seconds
maximumAge: 10 * 1000, // 10 seconds
enableHighAccuracy: true
};
var position = new Position();
$.mobile.loading('show');
// If the parameter requestType is 'set', the user wants to set
// his car position else he want to retrieve the position
if (requestType == 'set')
{
navigator.geolocation.getCurrentPosition(
function(location)
{
// Save the position in the history log
position.savePosition(
new Coords(
location.coords.latitude,
location.coords.longitude,
location.coords.accuracy
)
);
// Update the saved position to set the address name
Map.requestLocation(location);
Map.displayMap(location, null);
navigator.notification.alert(
'Your position has been saved',
function(){},
'Info'
);
},
function(error)
{
navigator.notification.alert(
'Unable to retrieve your position. Is your GPS enabled?',
function(){
alert("Unable to retrieve the position: " + error.message);
},
'Error'
);
$.mobile.changePage('index.html');
},
geolocationOptions
);
}
else
{
if (position.getPositions().length == 0)
{
navigator.notification.alert(
'You have not set a position',
function(){},
'Error'
);
$.mobile.changePage('index.html');
return false;
}
else
{
navigator.geolocation.watchPosition(
function(location)
{
// If positionIndex parameter isn't set, the user wants to retrieve
// the last saved position. Otherwise he accessed the map page
// from the history page, so he wants to see an old position
if (positionIndex == undefined)
Map.displayMap(location, position.getPositions()[0]);
else
Map.displayMap(location, position.getPositions()[positionIndex]);
},
function(error)
{
console.log("Unable to retrieve the position: " + error.message);
},
geolocationOptions
);
}
}
}
);
Initialize the Positions’ History Page
As soon as the user requires the file positions.html
, jQuery Mobile will start enhancing the components of the page. After the jQuery framework did its job, we need to retrieve the user’s previous locations and show them as a list. So, I’ll set a callback function to the pageinit
event of the position-page
element. The callback, will simply call the createPositionsHistoryList()
function that will be illustrated in a few moments.
$('#positions-page').live(
'pageinit',
function()
{
createPositionsHistoryList('positions-list', (new Position()).getPositions());
}
);
Create the Positions’ History List
As stated before, createPositionsHistoryList()
creates a list, item by item, using the stored position history and the Web Storage API. Each entry in the list has two actions that the user can run. The first action executes once the user touches the text of a previous location’s address, which will show it on the map in the same way that it displays the current car location when “Find car” is clicked. This is achieved by sending the same value (get
) to the map file (map.html
) for the requestType
parameter and by adding an additional one, called index
, that indicates which item of the locations’ history must be processed. If the connection isn’t available and a location is selected, the user will be notified of the connectivity problem and no other action will take place.
The second action is executed by touching the delete icon that is shown on the right side of the address for each item in the list. This, of course, will delete the selected item both from the list and the database. If there are any problems with deleting the item, the user will be warned.
The full source of createPositionsHistoryList()
is listed below.
/**
* Create the positions' history list
*/
function createPositionsHistoryList(idElement, positions)
{
if (positions == null || positions.length == 0)
return;
$('#' + idElement).empty();
var $listElement, $linkElement, dateTime;
for(var i = 0; i < positions.length; i++)
{
$listElement = $('<li>');
$linkElement = $('<a>');
$linkElement
.attr('href', '#')
.click(
function()
{
if (checkRequirements() === false)
return false;
$.mobile.changePage(
'map.html',
{
data: {
requestType: 'get',
index: $(this).closest('li').index()
}
}
);
}
);
if (positions[i].address == '' || positions[i].address == null)
$linkElement.text('Address not found');
else
$linkElement.text(positions[i].address);
dateTime = new Date(positions[i].datetime);
$linkElement.text(
$linkElement.text() + ' @ ' +
dateTime.toLocaleDateString() + ' ' +
dateTime.toLocaleTimeString()
);
// Append the link to the <li> element
$listElement.append($linkElement);
$linkElement = $('<a>');
$linkElement.attr('href', '#')
.text('Delete')
.click(
function()
{
var position = new Position();
var oldLenght = position.getPositions().length;
var $parentUl = $(this).closest('ul');
position.deletePosition($(this).closest('li').index());
if (oldLenght == position.getPositions().length + 1)
{
$(this).closest('li').remove();
$parentUl.listview('refresh');
}
else
{
navigator.notification.alert(
'Position not deleted. Something gone wrong so please try again.',
function(){},
'Error'
);
}
}
);
// Append the link to the <li> element
$listElement.append($linkElement);
// Append the <li> element to the <ul> element
$('#' + idElement).append($listElement);
}
$('#' + idElement).listview('refresh');
}
Running the Application
In the last section, all the pieces of the application were built and all the HTML, CSS, and JavaScript files were put in their place. So, now you’re ready to build and deploy “Where I parked my car”. But, you have to set the entry functions for the whole application. As you might have guessed, the function will be initApplication()
and it will run once Cordova is fully loaded. In this way, you can safely calls the Cordova APIs.
To achieve this goal we’ve set initApplication()
as a callback function for the deviceready
event. This is done by adding the following code to the index.html
file as you’ve already in its source listed in the second part of the series.
<script>
$(document).one('deviceready', initApplication);
</script>
Conclusion
In this article, I explained the last functions of the file function.js
. In the next and last part of this series, you’ll see how to create the configuration file (config.xml
), described in the first article for use with Adobe PhoneGap Build. The configuration file will help us to specify certain properties of the application like the author, the loading screens, the permissions, and so on. As you may expect, I’ll also publish the link to the repository where you can download the full source so that you can play with it. I’ll also share some final thoughts on what features you can add to further improve the application and what I hope the Cordova team will implement in the next releases that could take our app to the next level.