We Create Games

HTML5 Databases + Canvas

Posted June 19th by Jay Crossler in Code, iPhone, Maps, Video Editing, Web

I love HTML5! HTML5 is brand new – just available on a few browsers. The new features, especially the use of local SQL databases and canvases bring so much power to web application development. Before, things that you needed a full web server are now doable all within the browser. I think this is a game changer, especially for iPhone/gPhone development — we can now build much more complex mobile applications with much less code, and have them all work from the browser (rather than as applications specific to one platform).

There are some new security challenges, though. The drafters of the specification were pretty smart in thinking through some of these, which makes it more tricky to code things just right.

Below, I’ll show a demo that uses only a web page to:

  1. Go out to a web site, pass in the lat/long
  2. Get a list of images and download them
  3. Write the list of images to a local SQL database
  4. Serialize, and add the images themselves to the database
  5. Allow the user to unconnect their internet connection and still pull the images

You might note that this is the same technology that Google Gears offers… but built standard into the browser. Apple seems to really be embracing these technologies as an alternative to loading Gears or Flash onto the iPhone.

You can see the demo in action at: http://wecreategames.com/apps/imagesave/. Also check it out on your iPhone. Here’s a link to a zip file of all the source code.

Also, here’s a video summarizing how it all works:

Offline apps – The Manifest File

I’ve described the manifest file in the past as a way to tell the browser which files are important for caching and which shouldn’t be cached. For this app, we only have two files (index.html and imagesave.js) that need to be cached, and then two proxy files that are called to pull data about images and the images themselves. The manifest can do alot of other things, but we’re using it in it’s simplest form here:

CACHE MANIFEST
#ver 0.1
CACHE:
index.html
imagesave.js
NETWORK:
ws_image.php
ws.php

Working with the local browser database

The local database is a SQLlite database that’s built into Firefox, Safari, Opera, and Mobile Safari. It’s very powerful and easily makes sense if you’ve built SQL applications in the past. Very similar to Google Gears, the local database gives you a secure local storage of complex content. We’re going to use it to store metadata about the images we pull down (lat, longs, authors, etc) as well as serialized versions of the images themselves.

function picsInitDatabase() {
try {
if (!window.openDatabase) {
console.log('Databases are not supported in this browser');
} else {
var shortName = 'picsGeoDB';
var version = '1.0';
var displayName = 'Pictures Geotagged database';
var maxSize = 5000000; // in bytes
picsDB = openDatabase(shortName, version, displayName, maxSize);
console.log("Database is setup: "+picsDB);
}

In this instance, I’m telling the db to be 5Mb large. Any more than that and Mobile Safari asks for permission from the user.

Running SQL is the done through asynchronous transactions:
function picsCreateTables(){
picsDB.transaction(
function (transaction) {
/* The first query causes the transaction to (intentionally) fail if the table exists. */
transaction.executeSql('CREATE TABLE geopictures(id INTEGER NOT NULL PRIMARY KEY, secret TEXT NOT NULL DEFAULT "747060c06c", server INTEGER NOT NULL DEFAULT "3368",farm INTEGER NOT NULL DEFAULT "4", title TEXT NOT NULL DEFAULT "1 Infinite Loop", latitude TEXT NOT NULL DEFAULT "37.331666", longitude TEXT NOT NULL DEFAULT "-122.030834", accuracy INTEGER NOT NULL DEFAULT "16", datetaken TEXT NOT NULL DEFAULT "2009-06-14", ownername TEXT NOT NULL DEFAULT "avon11");', [], nullDataHandler, errorHandler);
transaction.executeSql('CREATE TABLE photodata(picid INTEGER NOT NULL PRIMARY KEY, encodedtext TEXT NOT NULL);', [], nullDataHandler, errorHandler);
}
);
}
function picsUpdateTables(dataID){
picsDB.transaction(
function (transaction) {
var p = data[dataID];
transaction.executeSql("INSERT INTO geopictures (id, secret, server, farm, title, latitude, longitude, accuracy, datetaken, ownername) VALUES (?,?,?,?,?,?,?,?,?,?);",
[p.id, p.secret, p.server, p.farm, p.title, p.latitude, p.longitude, p.accuracy, p.datetaken, p.ownername] );
transaction.executeSql("INSERT INTO photodata (picid, encodedtext) VALUES (?, ?)", [p.id, serializeCanvasByID(p.id)] );
}
);
}

It’s a bit tricky building your SQL, as you don’t get any detailed error messages on why things failed. I recommend you first build it on a local SQL Lite database and then copy in the SQL exactly, otherwise you’ll spend hours trying to track down small mistakes.

It’s very easy to call straight SQL commands, though make sure you don’t build the SQL on the fly and pass it in to be run. These local databases are susceptible to SQL injection attacks, just like any database. As you can see above, I’m passing in the values as an array.

SELECTing items is a little bit trickier, as you need to build some asynchronous calls. Also realize that you’re never sure of the order these calls will finish in.

function picsPhotodataSelectHandler(transaction, results)
{
console.log("Photodata had Results: "+results.rows.length);
// Handle the results
for (var i=0; i<results.rows.length; i++) {
var row = results.rows.item(i);
var newImage = new Object();
newImage.picid = row['picid'];
newImage.encodedtext = row['encodedtext'];
canvasData.push(newImage);
}
showImagesFromDB();
}
function picsSelectAll(){
picsDB.transaction(
function (transaction) {
transaction.executeSql("SELECT * from photodata;", [], picsPhotodataSelectHandler, errorHandler);
}
);
}

The above code runs a select statement, and passes in a function for what to do when things are finished successfully and another for when there is an error. The picsPhotodataSelectHandler is called once results are returned.

Drawing pixel by pixel in Javascript: the Canvas

The last piece of magic is to take images that we download, and to map them onto a canvas tag. The Canvas object is pretty amazing – it lets you either draw on it directly, or to read off individual pixels. You can also serialize the entire canvas out as an ascii string, which can then be saved to a database and later read back in. This is how we’re maintaining full control of our downloaded images. There are a few other methods of doing this that are a bit easier, but this gives you the most control.

function serializeCanvasByID(picID) {
var canvas = document.getElementById('cx'+picID);
var serializedVal = 'data:,';
if (canvas.toDataURL) serializedVal = canvas.toDataURL();
return serializedVal;
}
function drawImageOnCanvas(data, canvas) {
var img = new Image();
img.onload = function() {
canvas.width = img.width;
canvas.height = img.height;
canvas.getContext("2d").drawImage(img, 0, 0);
};
img.src = data;
}
function getCanvasDataByID(ID){
for (var i=0; i<canvasData.length;i++){
if (canvasData.picid == ID) {
return canvasData.encodedtext;
}
}
return '';
}
function showImagesFromDB() {
var container = document.getElementById('img-container')
container.innerHTML='';
for (var i=0; i<data.length; i++){
var canvasImg = document.createElement("canvas");
var imgID = data.id;
canvasImg.setAttribute("id", 'cx'+imgID);
canvasImg.setAttribute("title", data.desc);
drawImageOnCanvas(getCanvasDataByID(imgID), canvasImg);
container.appendChild(canvasImg);
}
}

The canvas.toDataURL(); call serializes out the image data. You can look at the string and see that it starts with “data:image/png” and then a bunch of characters.

The second piece of magic is in the drawImageOnCanvas function. It’s basically creating an img with the src either equal to a URL or to the “data:image/png…” text. Once the image is loaded into memory, it’s fed into the canvas’s 2d context to be shown on screen.

Summary

These three technologies can enable some pretty amazing things. You’ll be able to build very sophisticated web applications that are fully standards-compliant (so should hopefully work in iPhone, Android, Palm, etc). This could save thousands of $ as you no longer need to have experts in building apps for each platform. I could see this as being extremely valuable for making offline mapping applications.

(Back to last post in the series: Nearly2, iPhone app with 3D transforms)


11 comments to... “HTML5 Databases + Canvas”
Avatar
Henry

Your video is set to private. :(


Avatar
Jay Crossler

Henry, thanks! I just fixed the video to be public. Is that better?


Avatar
Scott

Hello Jay,

Thanks for a very informative video and tutorial. I realize that you created this almost a year ago but I’m just starting to dive into HTML5 now and I found your site from a Google search. I tried running the demo and it works great with Chrome.

I’m having a problem with the demo with Firefox 3.6.3. When I hit the index page I get the “Databases are not supported in this browser” message. Reading through your post it seems like the application should work with FF too. Has FF dropped support for the local DB since last July?

Thanks!
Scott


Avatar
Dale

I had no idea html5 would be able to do such things, I knew all about the tags that are relevant to semantic seo and that the browser vendors are re-writing their javascript engines to improve performance – i can now see why!

Great article.


[...] the HTML5 Database and Canvas article on this site, I delve into how to use the canvas to load images from the web, then save them [...]


Avatar
Madhava Jay

Awesome code, only problem im having is new Chrome seems to disallow cross domain canvas reading which puts a huge spanner in the works when using this across domian.

Now, I haven’t tested if it still works on a native app in iPhone like PhoneGap but it did work in the past. However due to webkit code sharing chances are this will be removed from the iPhone now or in the future.

do you have any ideas or solutions to get around the cross domain problem of reading canvas Data Uris to get the base64 encoded data.

The only idea I had was actually reading the image as base64 via ajax and then storing the string.


Avatar
Carlos

That is an excellent tutorial, I’ ve downloaded for try it myself. Can you tell me what is the IPhone simulator you use? Thanks!


Avatar
Chris J

Great video! I am trying to setup a database for an iphone app. I have created the database and the table fine but I am not sure how to populate alot of entries at once. Could you please explain (or show and example) of that?

Thanks so much!


Avatar
Axel Freudiger

Hey Jay,
nice work and thanks for sharing.

I implemented most parts but had to realize that .toDataURL() doesn’t work on Android Browser.
But there is a workaround that I’m going to try:
http://forum.xda-developers.com/showthread.php?t=1251575


Avatar
Axel Freudiger

So the mentioned aboved is working. I’m caching thumnail png graphics, so the data load is a little different then the ones in the example.


Avatar
John Elrick

The sample program is currently broken. I am testing with Chrome on Ubuntu 10.10 and the issue I am seeing is as follows:

Open the page.
Click on “Pull from Flickr”
No images appear

Tracing the JavaScript using the Developer Tools shows that in processReqChange, req.responseText is:

req.responseText: “?Deprecated: Assigning the return value of new by reference is deprecated in /home/bigyak/public_html/apps/imagesave/phpFlickr/phpFlickr.ph…

This call to

eval(req.responseText)

results in an “Uncaught SyntaxError: Unexpected token <"




(required)



(required) (Won't be displayed)


Your Comment: