Create a simple JavaScript stopwatch application that uses the Gears APIs on Android!
![]() |
![]() |
This tutorial describes how to create a simple JavaScript application that uses the APIs provided by Gears on Android. The sample application, called RunningMan, is a location-aware stopwatch that measures both the time and route taken for a journey, showing the journey on a map.
Though written specifically for Android, RunningMan can also be used on the other platforms that Gears supports as the APIs are the same. However, you may need to modify the HTML and CSS as Document Object Model support varies widely between browsers.
We recommend you first work through this tutorial using a desktop browser with good debugging capabilities. When you are happy with your application, test it using the Android emulator and finally try it out on a real Android device. Information about the Android emulator is available from http://code.google.com/android/reference/emulator.html
You can download the HTML and JavaScript source used in this tutorial from the Gears Resources and Tools page. Scroll down the page to the Resources, Samples and Tools download section then click Download Samples and Tools.
If you want to follow the tutorial step-by-step by rewriting the code yourself, which you are encouraged to do, you will probably want to still use the CSS file provided, along with the images. Note that the CSS file needs to be in a parent directory and the images to be in a parent images directory.
To complete this tutorial you will need to sign up for a Google Maps API development key. You will need a Google account to do this. Though your key should be delivered to you immediately, you may want to get it before starting this tutorial.
This section covers the basic HTML organization in RunningMan, and describes how to write a JavaScript stopwatch.
In terms of user interface, RunningMan appears as a front page with
buttons leading to other pages. In terms of HTML, those other pages are
hidden divisions in a single HTML page. JavaScript is held in a model.js
file.
The following HTML provides the basic skeleton for RunningMan. You
can type this, or copy and paste it into your favorite editor then save
the file. Alternatively, open the index.html file in the step1a
directory
of the zip download. Subsequent sections of this tutorial add to this file.
<html>
<head>
<title>RunningMan</title>
<link rel="stylesheet" type="text/css" href="../run.css" />
<script type="text/JavaScript" src="model.js"></script>
<meta name="viewport" content="width=320; user-scalable=false">
</head>
<body>
<div id="mainScreen" align="center">
<div id="title">
<img src="../images/icon.png" width="48" height="48">
<br>RunningMan<div id="subtitle">A simple Gears Stopwatch</div>
</div>
<div class="main-menu" align="right">
<div class="go-button" onclick="go('watch')">Stopwatch</div>
<div class="go-button" onclick="go('journeys')">Journeys</div>
</div>
</div>
<div id="watch" align="center">
<div class="content">
The stopwatch will be placed here
</div>
<div class="menu" align="left">
<div class="back-button" onclick="go('mainScreen')">Back</div>
</div>
</div>
<div id="journeys">
<div class="content">
Journeys will be placed here
</div>
<div class="menu" align="left">
<div class="back-button" onclick="go('mainScreen')">Back</div>
</div>
</div>
</body>
</html>
The model.js
file contains all the JavaScript code for RunningMan. The
following functions define the simple navigation mechanisms used:
/*
* Navigation functions
*/
function go(name) {
hideAllScreens();
var functionName = "on_" + name;
showDiv(name);
if (window[functionName] != null) {
window[functionName]();
}
}
function showDiv(name) {
var elem = document.getElementById(name);
if (elem) {
elem.style.display="block";
}
}
function hideDiv(name) {
var elem = document.getElementById(name);
if (elem) {
elem.style.display="none";
}
}
function hideAllScreens() {
hideDiv('mainScreen');
hideDiv('watch');
hideDiv('journeys');
}
Again, you can type this code, or copy and paste it into your favorite
editor then save the file. Alternatively, open the model.js
file in the
step1a
directory of the zip download.
By default, only the mainScreen division is shown, using CSS. When
the user clicks a button, the go()
function is called passing the parameter
associated with the button that was clicked (watch
, journeys
or mainscreen
).
The go()
function hides all divisions then shows the user-selected division.
If a function with the same name as the division, but prefixed by on_
exists,
then that function is called. This makes it easier to write functions handling
the behavior of the different screens and is described later in this tutorial.
Load
either your own HTML file or the index.html file from the step1a
directory
of the zip
download in a browser. You will see the main screen (image on the left
below). Click on the Stopwatch link to go to a blank screen (image on the
left below). Click on the Back button to return to the main screen.
![]() |
![]() |
The next step is to add a basic timer page to enable the user to start, stop, and reset the stopwatch and to display the elapsed time:
First declare a global object in model.js
:
var global = new Object();
Modify the HTML to call the main()
function when the page is loaded:
<body onload="main()">
Add the main()
function to model.js
:
/* * Main function */ function main() { global.startTime = null; updateTime(); }
The global.startTime
object contains the starting time of the stopwatch,
while the updateTime()
function displays the elapsed time.
Now modify the watch
division to add the HTML to call the startRun()
,
stopRun()
, and resetRun()
functions when the user clicks the corresponding
buttons:
<div id="watch" align="center">
<div class="content">
<div id="timeTitle">Time</div>
<div id="timeDisplay"></div>
<input type="button" onclick="startRun()" value="Start"></input>
<input type="button" onclick="stopRun()" value="Stop"></input>
<input type="button" onclick="resetRun()" value="Reset"></input>
</div>
<div class="menu" align="left">
<div class="back-button" onclick="go('mainScreen')">Back</div>
</div>
</div>
Add the startRun()
, stopRun()
and resetRun()
functions to model.js
:
/*
* Timer functions
*/
function startRun() {
global.updateInterval = setInterval("updateTime()", 1000);
global.startTime = new Date();
}
function stopRun() {
clearInterval(global.updateInterval);
}
function resetRun() {
stopRun();
global.startTime = null;
updateTime();
}
When the user clicks the Start button, the startRun()
function saves
the current time in the global object (global.startTime
), and updates the
elapsed time every second through the updateTime()
function; the stopRun()
function clears the timer object.
The updateTime()
function calls a formatTime()
function. Add both to
model.js
:
/*
* Display stopwatch time value
*/
function updateTime() {
var time = 0;
if (global.startTime != null) {
time = (new Date()).getTime() - global.startTime;
time = (time/1000)|0;
}
var timeDiv = document.getElementById("timeDisplay");
timeDiv.innerHTML = formatTime(time);
}
/*
* Format a time value given in seconds
*/
function formatTime(aTime) {
var seconds = aTime % 60;
var minutes = ((aTime / 60) |0) % 60;
var hours = (aTime / 3600) |0;
var time = "0";
if (seconds > 0) {
time = seconds + " s";
}
if (minutes > 0) {
time = minutes + " min " + time;
}
if (hours > 0) {
time = hours + " h " + time;
}
return time;
}
Load either your own HTML file or the index.html file from the step1b
directory of the zip download in a browser to see this in action.
It's now time to add Gears to our sample RunningMan application!
At the moment, the only way to launch the application is to either type the whole URL in a browser, or save it in the browser's bookmarks. Gears lets us create an icon on the Android desktop.
The first step is to import gears_init
in the HTML file (red text):
<head>
<title>RunningMan</title>
<link rel="stylesheet" type="text/css" href="../run.css" />
<script type="text/JavaScript" src="gears_init.js"></script>
<script type="text/JavaScript" src="model.js"></script>
<meta name="viewport" content="width=320; user-scalable=false">
</head>
Now, modify the main()
function by adding the red text
below to
model.js
, replacing YOUR_URL with
the URL for your files:
/*
* Main function
*/
function main() {
global.startTime = null;
updateTime();
global.siteUrl = "http://YOUR_URL/index.html";
installShortcut();
}
Finally, define the installShortcut()
function in model.js
, this function
uses the Gears
Desktop API to install a shortcut icon on the Android desktop:
/*
* Install shortcut
*/
function installShortcut() {
var desktop = google.gears.factory.create('beta.desktop');
desktop.createShortcut("RunningMan", global.siteUrl,
{ "48x48" : "../images/icon.png" }, "RunningMan application");
}
Refresh your page, or load either your own HTML file or the index.html
file from the step2
directory of the zip download in a browser. You should be greeted by a shortcut dialog:
Click Yes to accept the shortcut, then go back to the home screen, and you should see your icon. The following is an example from an Android device:
Clicking on the icon opens RunningMan in the browser. You can manipulate this shortcut in the same way as any other shortcut, move it to a different screen, put it in a folder, and so on.
With any application it is generally helpful to be able to save settings
or preferences. In it's current state, the RunningMan application asks
the user if they would like to create a shortcut icon every time it is
opened. It would be a much better experience for the user if RunningMan
did not repeatedly prompt them once the icon has been created.
To store this information, we will use the Gears Database
API. This API gives us access to a SQLite database, saved locally on
the device.
RunningMan needs to use a table to store preferences:
The first step is to add the following function to model.js
. This function
creates a beta
database and Preferences
table within that database:
/*
* Initialize the Database
*/
function initDB() {
global.db = google.gears.factory.create('beta.database');
global.db.open('stopwatch');
global.db.execute('CREATE TABLE IF NOT EXISTS Preferences ' +
'(Name text, Value int)');
}
Now add the following two utility functions to model.js
. These functions
set and get the value of a preference:
function getPreference(name) {
var result = false;
var rs = global.db.execute('SELECT Value FROM Preferences WHERE Name = (?)', [name]);
if (rs.isValidRow()) {
result = rs.field(0);
}
rs.close();
return result;
}
function setPreference(name, value) {
global.db.execute('INSERT INTO Preferences VALUES (?, ?)', [name, value]) }
Modify the installShortcut()
function to call the getPreference
and setPreference
functions in model.js
:
function installShortcut() {
if (getPreference('Shortcut') == false) {
var desktop = google.gears.factory.create('beta.desktop');
desktop.createShortcut("RunningMan", global.siteUrl,
{ "48x48" : "../images/icon.png" }, "RunningMan application");
setPreference('Shortcut', true);
}
}
Finally, modify the main()
function to call initDB()
:
function main() {
global.startTime = null;
updateTime();
global.siteUrl = "http://YOURURL/index.html";
initDB();
installShortcut();
}
RunningMan now only prompts the user once regarding installation of the shortcut icon.
The RunningMan application, for the moment, is still a very simple stopwatch. Wouldn't it be great if we could save each measured time?
This section adds the ability to save information in the following table:
First create a new Times
table to hold this information in initDB()
in model.js
:
function initDB() {
global.db = google.gears.factory.create('beta.database');
global.db.open('stopwatch');
global.db.execute('CREATE TABLE IF NOT EXISTS Preferences ' +
'(Name text, Value int)');
global.db.execute('CREATE TABLE IF NOT EXISTS Times ' +
'(StartDate int, StopDate int, Description text)');
}
Now modify the startRun()
and stopRun()
functions to save the date
and time information:
function startRun() {
global.updateInterval = setInterval("updateTime()", 1000);
global.startTime = new Date();
var time = global.startTime.getTime();
global.db.execute('INSERT INTO Times (StartDate) VALUES (?)',
[time]);
}
function stopRun() {
clearInterval(global.updateInterval);
var stopDate = new Date();
var time = stopDate.getTime();
global.db.execute('UPDATE Times SET StopDate = (?) ' +
'WHERE ROWID = (?)', [time,
global.db.lastInsertRowId]);
}
Now that date and time information is saved, we can display it in the Journeys screen. First, modify the HTML:
<div id="journeys">
<div class="content">
<div id="journeysContent"><h2>No journeys yet!</h2></div>
</div>
<div class="menu" align="left">
<div class="back-button" onclick="go('mainScreen')">Back</div>
</div>
</div>
Now add the on_journeys()
function to model.js
. This is called when
the user switches to the Journeys screen:
/*
* Show journeys
*/
function on_journeys() {
var rows = 0;
var html = "";
var rs = global.db.execute('SELECT ROWID, Description FROM
Times');
while (rs.isValidRow()) {
var rowID = rs.field(0);
var description = rs.field(1);
if (description == null) {
description = createDescription(rowID);
}
var rowType;
if (rows % 2 == 0) {
rowType = "rowA";
} else {
rowType = "rowB";
}
rows++;
html += "<div class='" + rowType + "'>";
html += "<div class='rowDesc'>" + description + "</div>";
html += "</div>";
rs.next();
}
if (html != "") {
var elem = document.getElementById('journeysContent');
elem.innerHTML = html;
}
}
The on_journeys()
function iterates through the saved journeys. The function first gets the description (if this is not in the database it is created by calling the createDescription()
). The on_journeys()
function then displays the description in a division using a CSS class of either rowA
or rowB
which are displayed in alternating colors.
Add the createDescription()
function to model.js
:
/*
* Create the text displayed in the Journeys pane for one record
*/
function createDescription(rowID) {
var description = "";
var rs = global.db.execute('SELECT StartDate, StopDate FROM Times
' + 'WHERE ROWID = (?)', [rowID]);
if (rs.isValidRow()) {
var sDate = rs.field(0);
var eDate = rs.field(1);
var time = (((eDate - sDate)/1000)|0); // elapsed seconds
var startDate = new Date();
startDate.setTime(sDate);
description = startDate.toLocaleDateString() + " -- ";
description += formatTime(time);
global.db.execute('UPDATE Times SET Description = (?) ' +
'WHERE ROWID = (?)', [description, rowID]);
}
return description;
}
Times are now automatically recorded, and going to the Journeys pane displays them:
Now that the application displays recorded times, it would be useful to be able to delete them.
Add a delete button by modifying the on_journeys
function:
html += "<div class='" + rowType + "'>";
html += "<div class='rowDesc'>" + description + "</div>";
html += "<div class='rowImg' onclick='deleteRecord(" + rowID + ")'>";
html += "<img src='delete.png'></div";
html += "</div>";
Add the deleteRecord()
function to model.js
:
/*
* Delete a recorded time
*/
function deleteRecord(rowID) {
var answer = confirm("Delete this run?");
if (answer) {
global.db.execute('DELETE FROM Times WHERE ROWID = (?)',
[rowID]);
go('journeys');
}
}
Refresh your page, or load either your own HTML file or the index.html
file from the step4
directory of the zip
download in a browser. The Journeys pane now looks like this:
RunningMan can now record times in the database, show the saved times, and remove them. Let's introduce a new API, Geolocation.
The Geolocation API enables RunningMan to record the position of the device on which it is running when the timer starts, save any intermediate positions, and save the position when the timer is stopped. Let's create a new database table to hold this information:
Add the following red text, to create a Positions
table, to model.js
:
function initDB() {
global.db = google.gears.factory.create('beta.database');
global.db.open('stopwatch');
global.db.execute('CREATE TABLE IF NOT EXISTS Preferences ' +
'(Name text, Value int)');
global.db.execute('CREATE TABLE IF NOT EXISTS Times ' +
'(StartDate int, StopDate int, Description text)');
global.db.execute('CREATE TABLE IF NOT EXISTS Positions ' +
'(TimeID int, Date int, Latitude float, ' +
' Longitude float, Accuracy float, Altitude float, ' +
' AltitudeAccuracy float)');
}
Now create a geolocation object using the main()
function, in model.js
:
function main() {
global.startTime = null;
global.geo = google.gears.factory.create('beta.geolocation');
updateTime();
global.siteUrl = "http://YOURURL/index.html";
initDB();
installShortcut();
}
When the timer starts RunningMan also needs to start a geolocation
watcher to receive a notification when the position changes and to save
the new position in the database. To do this, add the following red text
to model.js
:
function startRun() {
global.updateInterval = setInterval("updateTime()", 1000);
global.startTime = new Date();
var time = global.startTime.getTime();
global.db.execute('INSERT INTO Times (StartDate) VALUES (?)', [time]);
global.currentTimeID = global.db.lastInsertRowId;
global.currentGeoWatcher = global.geo.watchPosition(function
(position) {
global.db.execute('INSERT INTO Positions (TimeID, Date,
Latitude, ' +
'Longitude, Accuracy, Altitude, AltitudeAccuracy) ' +
'VALUES (?, ?, ?, ?, ?, ?, ?)',
[global.currentTimeID, position.timestamp.getTime(),
position.latitude, position.longitude,
position.accuracy,
position.altitude, position.altitudeAccuracy]);
}, null, { "enableHighAccuracy" : true});
}
Note that this does not deal with errors (it only passes a null parameter):
errors are simply discarded; the enableHighAccuracy
argument
forces the application to use the best available function, which is likely
to be the GPS on an Android G1 device.
Now modify the stopRun()
function to remove the geolocation watcher:
function stopRun() {
clearInterval(global.updateInterval);
var stopDate = new Date();
var time = stopDate.getTime();
global.db.execute('UPDATE Times SET StopDate = (?) ' +
'WHERE ROWID = (?)', [time, global.currentTimeID]);
if (global.currentGeoWatcher != null) {
global.geo.clearWatch(global.currentGeoWatcher);
global.currentGeoWatcher = null;
}
}
RunningMan also needs to modify the database update to use the global.currentTimeID
instead of the previous global.db.lastInsertRowId
, as many new inserts
could potentially have happened.
Finally, the additional information also needs to be deleted when a timing has been removed:
function deleteRecord(rowID) {
var answer = confirm("Delete this run?");
if (answer) {
global.db.execute('DELETE FROM Positions WHERE TimeID = (?)',
[rowID]);
global.db.execute('DELETE FROM Times WHERE ROWID = (?)',
[rowID]);
go('journeys');
}
}
Now that RunningMan saves position information, the description displayed in the Journeys screen can be improved to give, for example, the number of positions saved, the distance travelled, and the average speed.
Modify the createDescription()
function to call a positionInformation()
function in model.js
:
description = startDate.toLocaleDateString() + " -- ";
description += formatTime(time);
description += positionInformation(rowID);
The positionInformation()
function iterates through the associated
positions, and returns the additional information. Add the positionInformation()
function to model.js
:
/*
* For a given row, returns distance traveled, average speed,
* and number of positions saved
* /
function positionInformation(rowID) {
var distance = 0;
var prevLat = null;
var prevLon = null;
var firstTime = 0;
var lastTime = 0;
var nbPositions = 0;
var rs = global.db.execute('SELECT Latitude, Longitude, Date ' +
'FROM Positions WHERE TimeID = (?) ' +
'ORDER BY Date', [rowID]); while (rs.isValidRow()) { nbPositions++; var lat = rs.field(0); var lon = rs.field(1); var date = rs.field(2); if (firstTime != 0) { distance += haversineDistance(prevLat, prevLon, lat, lon); } else { firstTime = date; } prevLat = lat; prevLon = lon; lastTime = date; rs.next(); } var secTime = (lastTime - firstTime) / 1000; var averageSpeed = ((distance * 3600) / secTime); var roundedDistance = (((distance*1000)|0)/1000); var roundedSpeed = ((averageSpeed*1000)|0)/1000; var description = " (" + roundedDistance + " km)"; description += "<br>Average speed: " + roundedSpeed + " km/h"; description += "<br>" + nbPositions + " positions saved"; return description; }
To compute the distance between the coordinates, RunningMan uses the following functions, add these to model.js
:
/* * Utility function to compute the distance between * two coordinates */ Number.prototype.toRad = function() { // convert degrees to radians return this * Math.PI / 180; } function haversineDistance(lat1, long1, lat2, long2) { // from http://www.movable-type.co.uk/scripts/latlong.html var R = 6371; // km var dLat = (lat2-lat1).toRad(); var dLon = (long2-long1).toRad(); var a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(lat1.toRad()) * Math.cos(lat2.toRad()) * Math.sin(dLon/2) * Math.sin(dLon/2); var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); var d = R * c; return d; }
For more information on calculating distance and bearing between two points see http://www.movable-type.co.uk/scripts/latlong.html.
Refresh your page, or load either your own HTML file or the index.html
file from the step6
directory of the zip
download in a browser. Going back to the Journeys screen, you should
now see the new information, for example:
This section covers using the Google Maps API to plot movement on a map.
If you haven't already done so, you need to sign up for a Google Maps API development key. When you have your key, add the following red text to your HTML file, replacing YOURKEY with your actual Maps API key:
<script type="text/JavaScript" src="gears_init.js"></script>
<script src="http://maps.google.com/maps?file=api&v=2&key=YOURKEY"
type="text/JavaScript"></script>
<script type="text/JavaScript" src="model.js"></script>
Note: Google Maps API keys are dependent on the server's URL.
Now add a call to the unload function:
<body onload="main()" onunload="GUnload()">
RunningMan can now use the JavaScript functions of the Maps API!
The next step is to add a new division section to the end of the HTML file. This displays the map, plus a row of buttons used for navigation:
<div id="location" align="center">
<div class="content">
<div id="mapLabel">Map</div>
<div id="map" style="width: 300px; height: 300px"></div>
</div>
<div class="menu" align="left">
<div class="back-button"
onclick="go('mainScreen')">Back</div>
</div>
<div id="map-buttons" align="left">
<div class="button-left" onclick="mapLeft()"></div>
<div class="button-right" onclick="mapRight()"></div>
<div class="button-up" onclick="mapUp()"></div>
<div class="button-down" onclick="mapDown()"></div>
<div class="button-zoomin" onclick="mapZoomIn()"></div>
<div class="button-zoomout" onclick="mapZoomOut()"></div>
</div>
</div>
</body>
</html>
Add this division to the hideAllScreens()
function in model.js
:
function hideAllScreens() {
hideDiv('mainScreen');
hideDiv('watch');
hideDiv('journeys');
hideDiv('location');
}
Now modify the main()
function in model.js
to set the
default mapZoom
value:
function main() {
global.startTime = null;
global.geo = google.gears.factory.create('beta.geolocation');
global.mapZoom = 15;
updateTime();
global.siteUrl = "http://YOURURL/index.html";
initDB();
installShortcut();
}
Add the following functions, that provide navigation, to model.js
:
/* * Map navigation functions */ function mapLeft() { global.map.panDirection(1, 0); } function mapRight() { global.map.panDirection(-1, 0); } function mapUp() { global.map.panDirection(0, 1); } function mapDown() { global.map.panDirection(0, -1); } function mapZoomIn() { global.map.zoomIn(); } function mapZoomOut() { global.map.zoomOut(); }
Now, add a showMap()
function to model.js
. This function:
/* * Extract position information and plot * */ function showMap(rowID) { hideAllScreens(); showDiv('location'); var lastPoint = null; global.map = new GMap2(document.getElementById("map")); var locations = new Array(); var rs = global.db.execute('SELECT Latitude, Longitude ' + 'FROM Positions WHERE TimeID = (?) ' + 'ORDER BY Date', [rowID]); while (rs.isValidRow()) { var lat = rs.field(0); var lon = rs.field(1); var point = new GLatLng(lat, lon); locations[locations.length] = point; lastPoint = point; rs.next(); } if (lastPoint != null) { global.map.setCenter(lastPoint, global.mapZoom); var polyline = new GPolyline(locations, '#ff0000', 8); global.map.addOverlay(polyline); global.map.addOverlay(new GMarker(point)); } }
The final step is to call the showMap()
function by modifying the on_journeys()
function in model.js
:
html += "<div class='" + rowType + "'>";
html += "<div class='rowDesc' onclick='showMap(" + rowID + ")'>";
html += description + "</div>";
html += "<div class='rowImg' onclick='deleteRecord(" + rowID + ")'>";
html += "<img src='../images/delete.png'></div";
html += "</div>";
Refresh your page, or load either your own HTML file or the index.html file from the step7
directory of the zip download in a browser. As long as you have previously made some timing recordings, if you go to the Journeys page and click on one item, you should see a map of your location.
The following is an example of starting the stopwatch on an Android device on Buckingham Palace Road in London then stopping it having moved along Lower Belgrave street:
The Gears LocalServer API enables a web application to save its resources locally. This reduces latency (as there is no need to fetch resources from the network) and allows the application to work without a network connection. RunningMan uses a network connection only to obtain maps through the Google Maps API, aside from this, it could work completely disconnected by relying only on the GPS functionality to detect location.
There is also a further complication: the Maps API JavaScript loads directly into the HTML header, if the linked file is not in the browser's cache, the page will block. This file cannot be stored locally as it is from a different location than our application, and Gears enforces a same-location security policy. In addition, the Maps' JavaScript issues requests to other files it needs and is not entirely contained in this single file. The workaround is to load the Google Maps API lazily, so that RunningMan can asynchronously load the JavaScript, and fail gracefully if the API cannot be loaded.
To use the managed store functionalities to save resources locally, an application needs a manifest file that lists all the files to be saved and served, transparently, by Gears. For RunningMan, this file is named manifest.json the contents of which are provided below:
{ // version of the manifest file format "betaManifestVersion": 1, // version of the set of resources described in this manifest file "version": "version 1.0", // URLs to be cached (URLs are given relative to the // manifest URL) "entries": [ { "url": "index.html" }, { "url": "gears_init.js" }, { "url": "model.js" }, { "url": "../run.css" }, { "url": "../images/icon.png" }, { "url": "../images/button-background.png" }, { "url": "../images/background.png" }, { "url": "../images/back-arrow.png" }, { "url": "../images/go-arrow.png" }, { "url": "../images/delete.png" }, { "url": "../images/left.png" }, { "url": "../images/right.png" }, { "url": "../images/down.png" }, { "url": "../images/up.png" }, { "url": "../images/zoomIn.png" }, { "url": "../images/zoomOut.png" } ] }
RunningMan needs to call the checkForUpdate()
function for
Gears to automatically download the files and keep them up-to-date. Add
the following function to model.js
:
/*
* Capture resources for offline mode
*/
function captureOffline() {
global.localserver = google.gears.factory.create("beta.localserver");
global.store = global.localserver.createManagedStore("Stopwatch");
global.store.manifestUrl = "manifest.json";
global.store.checkForUpdate();
}
To load the Google Maps API asynchronously, you need to add the following
function to model.js
(don't forget to replace YOURKEY with
the corresponding Google Maps API key for your server):
/*
* Loads the Google Maps API asynchronously
*/
function loadMapsAPI() {
var script = document.createElement("script");
var key = 'YOURKEY';
script.type = "text/javascript";
script.src = "http://maps.google.com/maps?file=api&v=2&async=2&key=" + key;
document.body.appendChild(script);
}
Now add a call to captureOffline()
in the main()
function in model.js
, the last two lines enable RunningMan to load maps asynchronously:
initDB();
installShortcut();
captureOffline();
loadMapsAPI();
So that RunningMan will bail out gracefully if the Maps API is not
loaded, add
a check to the the showMap()
function
in model.js
:
function showMap(rowID) {
hideAllScreens();
showDiv('location');
if (window["GMap2"] == null) {
alert('No Map object!');
return;
}
Finally, remove the following line, which was added in Step
7, from the header in index.html
:
<script src="http://maps.google.com/maps?file=api&v=2&key=YOURKEY"
type="text/JavaScript"></script>
The header in index.html
now looks like this:
<head id="head">
<title>RunningMan</title>
<link rel="stylesheet" type="text/css" href="../run.css" />
<script type="text/javascript" src="gears_init.js"></script>
<script type="text/javascript" src="model.js"></script>
<meta name="viewport" content="width=320; user-scalable=false">
</head>
That's it! You can either refresh your page, or load either your own
HTML file or the index.html file from the step8
directory of the zip
download in a browser.
For information on each of the Gears APIs used in RunningMan follow the links below:
For information on: