This tutorial is designed to show how to use multiple Gears APIs in conjunction. It shows how to create an offline application that performs operations in the background with WorkerPool and utilizes the client-side database. The actual application that is created is a simple stock monitor that allows the user to specify stock tickers (e.g. "goog") to be viewed in realtime or offline.
This tutorial will cover these Gears APIs:
This section will cover how to cache application files offline. After this step is complete you will have a page whose HTML, JavaScript & CSS can be accessed offline.
The first step is including gears_init.js
in stock_tutorial.html
. This initializes the google.gears.factory
object into the browser's DOM and enables Gears functionality (if the user has Gears installed). Next, we set the body onLoad
event to call init()
which is in stock_ticker.js
.
We will put our JavaScript code in stock_ticker.js
. The first step is to create an init
function that will check if the user has Gears installed on their browser. If they don't, then the code will redirect the user to the gears.google.com page to ask them if they would like to install Gears. Notice that in the URL there are CGI arguments which enable the developer to have a custom message appear on the install page as well as a redirect-upon-install URL. These arguments are message
and return
. If the user does have Gears installed then the code will create an instance of the LocalServer
class -- the container of ManagedResourceStore
and ResourceStore
. You can learn more about the CGI args here.
function init() { if(!window.google || !google.gears) { location.href = "http://gears.google.com/?action=install&message=" + "Stock ticker requires Google Gears to be installed." + "&return=" + location.href; return; } else { localServer = google.gears.factory.create("beta.localserver"); } createManagedStore(); }
This function creates an instance of ManagedResourceStore
, specifies a manifest file whose contents will be cached, sets a callback function that will refresh the page when the contents are captured, and then initiates the capture.
function createManagedStore() { managedStore = localServer.createManagedStore(MANAGED_STORE_NAME); managedStore.manifestUrl = MANIFEST_FILENAME; managedStore.oncomplete = function(details) { if (details.newVersion != '') { location.href = location.href; } } managedStore.checkForUpdate(); }If you want to go a step further and be clued in on the status of the capture, try using the
ManagedResourceStore
callback methods. They allow you to set functions to be called when certain events fire during the capture. Try adding this code before checkForUpdate()
:
managedStore.onerror = function(error) { alert("Error: " + error.message); } managedStore.onprogress = function(details) { alert('Have downloaded ' + details.filesComplete + '/' + details.filesTotal + ' files.'); }
The ManagedResourceStore
will download all files we specify in the manifest file. Take a look at stock_ticker_manifest.json
and you will see the JSON (JavaScript Object Notation) that specifies the version and list of files that should be cached on users' machines. For more information on creating a manifest file please read this reference page.
{ "betaManifestVersion": 1, "version": "version 1", "entries": [ { "url": ".", "src": "stock_ticker.html"}, { "url": "stock_ticker.html"}, { "url": "stock_ticker.css"}, { "url": "stock_ticker.js"}, { "url": "gears_init.js"} ] }
Now try running the code (you can find step1 code here). After visiting stock_ticker.html
once you should have the resources stored (and if you used the callbacks you will know when they are done downloading). Try putting your browser in 'offline' mode or disabling your internet connection, clear you browser cache and then reload stock_ticker.html
. It should now work offline.
In this section we will use Gears' HTTPRequest
& Database
APIs to make the application grab stock quotes and store them in the database to be viewable offline.
The first step is to create a database where we can store our information for offline access. The steps are simple - create an instance of the Database
class, open a database with database.open(name)
(Gears creates one if it doesn't exist) and then execute a table creation on the database. There are only 3 fields per entry in the database - symbol, price and change. To make things straightforward they are all TEXT types, though any type that SQLite allows can be used. Now we are ready to insert/update/select/delete.
function createDatabase() { database = google.gears.factory.create("beta.database"); database.open(DATABASE_NAME); database.execute('create table if not exists ' + DB_TABLE_NAME + ' (symbol TEXT, price TEXT, change TEXT)'); }
To display the stocks when the application is loaded we will select all entries from the database. That code is located in init()
. It uses a function selectAllStocks
which runs a database query to grab all of the entries in the database:
database.execute('SELECT * FROM ' + DB_TABLE_NAME);Then it loops through the results and adds each entry to the HTML.
var rs = selectAllStocks(); while(rs.isValidRow()) { var stock = { symbol : rs.field(0), price : rs.field(1), change : rs.field(2) } updateHTMLTable(stock); rs.next(); } rs.close()
Create an HTML input element in stock_ticker.html
that has an onkeydown
listener. Whenever a user hits "enter" in the input box getStock
will be called. The getStock
function will create a Gears HTTPRequest and send a query to a Yahoo service for stock quotes. When the results are received they will be inserted into the database and the HTML table.
There are a few things to note about the request made in getStock
:
yahoo_ticker_proxy.php
. If you aren't familiar with a proxy like this, the basic idea of it is that we are going to make an XMLHttpRequest to the PHP script which will make a request to Yahoo for the stock ticker.random
. It is set to a random number between 0 and 1,000,000. This is done because browsers cache requests that are made but the stock ticker needs fresh stock quotes. By adding in a random number (using (new Date()).getTime()
is also common) the browser will never serve requests from cache.onreadystatechange
method is the callback method that is triggered when the request changes states. One of these state changes, state 4, signifies that the request has completed.getStock
:
function getStock(ticker) { var request = google.gears.factory.create('beta.httprequest'); // This will keep the browser from cacheing our requests to Yahoo Finance var dontCacheRequest = '&random=' + Math.floor(Math.random()*1000000); request.open('GET', 'yahoo_ticker_proxy.php?q=http://download.finance' + '.yahoo.com/d/quotes.csv?s=' + ticker + '&f=sl1d1t1c1ohgv&e=.csv' + dontCacheRequest); request.onreadystatechange = function() { if (request.readyState == 4) { var stockArray = request.responseText.split(','); var stock = { symbol : ticker, value : stockArray[1], change : stockArray[4] } insertIntoDatabase(stock); updateHTMLTable(stock); } }; request.send(); }
Upload the new code and update the version string in the manifest file so that the new files will be cached by Gears.
This section will cover how to use the Gears WorkerPool
in order to complete tasks in the background.
Currently the application only grabs stock values upon loading. With WorkerPool
we can have the quotes updated frequently in the background. Think of WorkerPool
as threading for JavaScript. Gears' Timer
module will be needed because workers do not have access to the window object which has the traditional methods for timers, setTimeout()
and setInterval()
.
The strategy will be to spawn a single WorkerPool
worker that will monitor all stock quotes for us. This will also serve as our database manager process. It is good practice to use a single WorkerPool
worker to be the only interaction point with a database -- it is more performant and it eliminates database access concurrency issues.
Think of a WorkerPool
instance as the parent thread and all created workers as the child threads. The WorkerPool
will tell the workers what they need to do, the workers will complete the tasks and then report back the result. To communicate, the WorkerPool
and workers send messages to each other. Sending messages is handled by sendMessage()
and received messages are handled by the onmessage()
callback.
In this code, the createWorkerPool()
function handles the creation of the application's WorkerPool. It also sets the onmessage()
handler to be a function that will receive a stock ticker, price and %change which will write those values to the HTML. As you can see, the onmessage()
handler will be passed a messageObject
. This object consists of a message and a sender ID. The message can be a JavaScript object, a string, etc. Check out the reference for the messageObject
.
function createWorkerPool() { workerPool = google.gears.factory.create('beta.workerpool'); workerPool.onmessage = function(a, b, messageObject) { if (messageObject.body.operation == 'removedTicker') { removeFromHTMLTable(messageObject.body.symbol); } if (messageObject.body.operation == 'addedTicker') { var stock = { 'symbol' : messageObject.body.symbol, 'price' : messageObject.body.price, 'change' : messageObject.body.change }; updateHTMLTable(stock); } }; }
The application will now create a single worker to monitor all stock tickers efficiently.
In this application, the createWorker
function handles creating a new worker. This function calls the Gears method WorkerPool.createWorkerFromUrl()
to initialize a worker. However, the worker won't do anything until a message is sent to it. We call addStocks
to send a message to the worker to start monitoring new stocks. In this function workerPool.sendMessage
is used to send that message. The first parameter sent is the JSON message, the second parameter is the child worker's ID which is how the message is sent to the correct worker. This message gets received by worker.js
in the onmessage
callback function.
function createWorker(symbols) { workerId = workerPool.createWorkerFromUrl('worker.js'); addStocks(symbols); } function addStocks(symbols) { var message = { 'operation' : 'addTickers', 'symbols' : symbols } workerPool.sendMessage(message, workerId); }
When the worker is initialized it will run until the application is exited. It has an array that keeps track of all stocks it should be monitoring and it checks every 3 seconds to see if there are stocks to update. If there are it will do a request for their values and return the results to the parent WorkerPool
.
The stocks that the worker monitors can be changed by the WorkerPool
in stock_ticker.js
. The worker receives a message in the onmessage
handler and it switches on the operation
property to decide whether to add or remove a stock from its monitoring list.
There are three scenarios:
tickers
array since there is a timer that automatically checks the tickers
array every 3 seconds.
wp.onmessage = function(a, b, messageObject) { var operation = messageObject.body.operation; if (operation == 'addTickers') { var stockSymbols = messageObject.body.symbols; parent = messageObject.sender; // If the worker was just created for the first time if (database == null) { createDatabase(); tickers = stockSymbols; timer.setTimeout(getStocks, 10); } else { // If the worker exists and we are adding new symbols for (var i=0; i < stockSymbols.length; i++) { tickers.push(stockSymbols[i]); } } } if (operation == 'removeTicker') { var symbolToRemove = messageObject.body.symbol; removeStock(symbolToRemove); for (var j=0; j < tickers.length; j++) { var curTicker = tickers[j]; if (symbolToRemove == curTicker) { // When we cut it out of the array it won't get polled next time the // HTTPRequest is made for stocks tickers.splice(j, 1); } } } };
Now take a peek at the code that monitors the stocks in the background -- getStocks
. It is fairly straightforward since it is only a slightly modified version of Part 2's getStock
function. The only difference is that this one uses wp.sendMessage(stock, parent)
to report stock values back to the parent WorkerPool
and then uses timer.setTimeout(getStocks, 3000)
(a 3 second timer) to schedule the next stock quote grab.
function getStocks() { // Gotta have stocks to monitor! if (tickers.length > 0) { var request = google.gears.factory.create('beta.httprequest'); // Let's keep the requests returning fresh results. var randomNum = Math.floor(Math.random()*1000000); var tickersReq = tickers.join('%7C'); // %7C is the up and down | symbol // One url request for multiple stocks var url = 'yahoo_ticker_proxy.php?q=http://download.finance.yahoo.com/d/quotes.csv?s='+tickersReq+'&f=sl1d1t1c1ohgv&e=.csv&random=' + randomNum; request.open('GET', url); request.onreadystatechange = function() { if (request.readyState == 4) { // Split the stocks var stockResults = request.responseText.split('\n'); // Forget the last newline, it doesn't have a stock in it stockResults.splice(stockResults.length - 1, 1); for (var i=0; i < stockResults.length; i++) { // Split up the details of this stock var stockDetails = stockResults[i].split(','); var stock = { 'operation' : 'addedTicker', 'symbol' : stockDetails[0].replace(/"/g, ''), 'price' : stockDetails[1], 'change' : stockDetails[4] }; insertIntoDatabase(stock); if (typeof stock.price != 'undefined' && typeof stock.change != 'undefined') { wp.sendMessage(stock, parent); } } } }; request.send(); } timer.setTimeout(getStocks, 3000); }
Congratulations, you now have the know-how to implement 5 of the Gears modules! Upload your code, increment the manifest file's version number and go play around with it!