Article Index |
---|
webOS HTML5 Database Storage Tutorial |
The Constructor Function |
Prototyping in webOS |
Finishing up |
All Pages |
webOS skill level: Beginner
Technologies covered: HTML5, SQL, Javascript
Prerequisite knowledge: Intermediate-level HTML, CSS, and Javascript
With the release of Chapter 1 of Palm webOS by O'Reilly, Palm has confirmed that local storage will indeed be handled by HTML5's new local storage functionality.
If you haven't been able to find any tutorials on HTML5's storage capability, you're not alone. After looking around, we realized that the HTML5 spec is still at such an early revision that there are few resources out there that describe how it should be used. But with a little digging, we found this excellent little HTML5 database application over at webkit.org. We eagerly grabbed the source code, deconstructed it, and we're proud to bring you the first webOS / HTML5 database storage tutorial!
While we (obviously) haven't tested this application on an actual webOS device, we feel that there's a good chance it would work as-is in the Pre's web browser, as the Pre's browser is webkit-based (although whether the drag & drop functionality would work without modification is unclear at this point). Check out the end of this article for notes on how we might turn this web-based application into a native application that you could launch from the Pre's launcher screen.
Let's start from the top, shall we? First of all, if you haven't seen the application yet, try it out here. Get a good feel for how it behaves, then come back here and dive into the code.
<!doctype html>
<html manifest="DatabaseExample.manifest">
<head>
<title>WebKit HTML 5 SQL Storage Notes Demo</title>
<style>
Right off the bat we find a new HTML5 attribute in the html tag. The html manifest attribute gives the address of the document's application cache manifest. There's a very technical explanation of an application cache here, but I'm going to surmise that it's basically a set of cached resources associated with a particular application, and the manifest describes the contents of that application. Next comes:
body {
font-family: 'Lucida Grande', 'Helvetica', sans-serif;
}
.note {
background-color: rgb(255, 240, 70);
height: 250px;
padding: 10px;
position: absolute;
width: 200px;
-webkit-box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.5);
}
.note:hover .closebutton {
display: block;
}
.closebutton {
display: none;
background-image: url(deleteButton.png);
height: 30px;
position: absolute;
left: -15px;
top: -15px;
width: 30px;
}
.closebutton:active {
background-image: url(deleteButtonPressed.png);
}
.edit {
outline: none;
}
.timestamp {
position: absolute;
left: 0px;
right: 0px;
bottom: 0px;
font-size: 9px;
background-color: #db0;
color: white;
border-top: 1px solid #a80;
padding: 2px 4px;
text-align: right;
}
Nothing particularly new here; some styles that get applied to elements of the document, including a webkit-specific style declaration -webkit-box-shadow that will theoretically work on the Pre because it uses a webkit-based browser. Now to the interesting stuff:
var db;
try {
if (window.openDatabase) {
db = openDatabase("NoteTest", "1.0", "HTML5 Database API example", 200000);
if (!db)
alert("Failed to open the database on disk. This is probably because the version was bad or there is not enough space left in this domain's quota");
} else
alert("Couldn't open the database. Please try with a WebKit nightly with this feature enabled");
} catch(err) { }
Let's break this down even further and look at what each line is doing:
try { if (window.openDatabase) { ... } else alert("Couldn't open the database. Please try with a WebKit nightly with this feature enabled"); } catch(err) { }
These enclosing statements are attempting to determine whether the window object of the current browser (or, in our case, version of webOS) supports the the HTML5 openDatabase method. The try statement will catch an error if the any of the statements enclosed cause the interpreter to throw an error.
If you're developing for the Pre or another webOS device, you may decide to leave checks like these out to minimize the size of your application, but they can also provide valuable debugging information if your application is not behaving as you expect.
db = openDatabase("NoteTest", "1.0", "HTML5 Database API example", 200000); if (!db) alert("Failed to open the database on disk. This is probably because the version was bad or there is not enough space left in this domain's quota");
Here we're calling a new HTML5 window method openDatabase. This method returns a database object, which we are assigning to the variable "db". The openDatabase method takes 4 arguments:
- database name
- database version
- display name
- estimated size, in bytes of the data that will be stored in the database
In this case, the database name is NoteTest, the version is 1.0, the database display name is HTML5 Database API example and the size of the data is 200kb (approximately; 1kb=1024bytes). The database name parameter specifies the name you will use to reference the database in the code, and the display name is probably how the database will be displayed in some sort of database browser that might be bundled with HTML5 compatible browsers.
Next, we get into the fun stuff!
Here we're setting up a few variables that will help us manage the database during the use of our application:
var captured = null; var highestZ = 0; var highestId = 0;
The Constructor
function Note() { var self = this; var note = document.createElement('div'); note.className = 'note'; note.addEventListener('mousedown', function(e) { return self.onMouseDown(e) }, false); note.addEventListener('click', function() { return self.onNoteClick() }, false); this.note = note; var close = document.createElement('div'); close.className = 'closebutton'; close.addEventListener('click', function(event) { return self.close(event) }, false); note.appendChild(close); var edit = document.createElement('div'); edit.className = 'edit'; edit.setAttribute('contenteditable', true); edit.addEventListener('keyup', function() { return self.onKeyUp() }, false); note.appendChild(edit); this.editField = edit; var ts = document.createElement('div'); ts.className = 'timestamp'; ts.addEventListener('mousedown', function(e) { return self.onMouseDown(e) }, false); note.appendChild(ts); this.lastModified = ts; document.body.appendChild(note); return this; }
This is the constructor function for a Note object. (For information on Javascript constructors, check out this article.) This function sets up an actual note object by creating each of the elements that make up a note and hooking up the various interactions that a user might make with each element. Let's take a closer look.
var self = this;
This sets the variable self to refer to this particular instance of the Note object, identified by the this keyword. For more info on the this keyword, check out this great article.
var note = document.createElement('div'); note.className = 'note'; note.addEventListener('mousedown', function(e) { return self.onMouseDown(e) }, false); note.addEventListener('click', function() { return self.onNoteClick() }, false); this.note = note;
These lines create the note div element and gives it the CSS class note, which was defined earlier. It then adds event listener functions for two mouse events: mousedown and click, which call functions onMouseDown and onNoteClick, respectively. We'll see what these functions do a bit later.
var close = document.createElement('div'); close.className = 'closebutton'; close.addEventListener('click', function(event) { return self.close(event) }, false); note.appendChild(close);
This section sets up the close element and adds a click event listener that calls the function close(). It assigns it a css class (closebutton) and then appends the close element as a child of the Note object instance.
var edit = document.createElement('div'); edit.className = 'edit'; edit.setAttribute('contenteditable', true); edit.addEventListener('keyup', function() { return self.onKeyUp() }, false); note.appendChild(edit); this.editField = edit;
This section sets up the edit object. The HTML5 contenteditable attribute is set on this div so that the user can type into the div to change its contents. An event listener is added onKeyUp, probably to save the contents of the note in real-time (i.e. each time a key is pressed). We'll see later.
var ts = document.createElement('div'); ts.className = 'timestamp'; ts.addEventListener('mousedown', function(e) { return self.onMouseDown(e) }, false); note.appendChild(ts); this.lastModified = ts;
This section sets up the timestamp object (shortened to ts). This function sets up the timestamp that is displayed at the bottom of the note.
The next section defines additional methods for Note objects through the use of prototyping. Prototyping is an important concept in the Javascript world because it allows us to add additional methods to objects that have already been defined (in our case, the Note object). Let's see how this is done.
Prototyping
Here's the full prototype function. Have a look at it in its entirety, then we'll break it down.
Note.prototype = { get id() { if (!("_id" in this)) this._id = 0; return this._id; }, set id(x) { this._id = x; }, get text() { return this.editField.innerHTML; }, set text(x) { this.editField.innerHTML = x; }, get timestamp() { if (!("_timestamp" in this)) this._timestamp = 0; return this._timestamp; }, set timestamp(x) { if (this._timestamp == x) return; this._timestamp = x; var date = new Date(); date.setTime(parseFloat(x)); this.lastModified.textContent = modifiedString(date); }, get left() { return this.note.style.left; }, set left(x) { this.note.style.left = x; }, get top() { return this.note.style.top; }, set top(x) { this.note.style.top = x; }, get zIndex() { return this.note.style.zIndex; }, set zIndex(x) { this.note.style.zIndex = x; }, close: function(event) { this.cancelPendingSave(); var note = this; db.transaction(function(tx) { tx.executeSql("DELETE FROM WebKitStickyNotes WHERE id = ?", [note.id]); }); var duration = event.shiftKey ? 2 : .25; this.note.style.webkitTransition = '-webkit-transform ' + duration + 's ease-in, opacity ' + duration + 's ease-in'; this.note.offsetTop; // Force style recalc this.note.style.webkitTransformOrigin = "0 0"; this.note.style.webkitTransform = 'skew(30deg, 0deg) scale(0)'; this.note.style.opacity = '0'; var self = this; setTimeout(function() { document.body.removeChild(self.note) }, duration * 1000); }, saveSoon: function() { this.cancelPendingSave(); var self = this; this._saveTimer = setTimeout(function() { self.save() }, 200); }, cancelPendingSave: function() { if (!("_saveTimer" in this)) return; clearTimeout(this._saveTimer); delete this._saveTimer; }, save: function() { this.cancelPendingSave(); if ("dirty" in this) { this.timestamp = new Date().getTime(); delete this.dirty; } var note = this; db.transaction(function (tx) { tx.executeSql("UPDATE WebKitStickyNotes SET note = ?, timestamp = ?, left = ?, top = ?, zindex = ? WHERE id = ?", [note.text, note.timestamp, note.left, note.top, note.zIndex, note.id]); }); }, saveAsNew: function() { this.timestamp = new Date().getTime(); var note = this; db.transaction(function (tx) { tx.executeSql("INSERT INTO WebKitStickyNotes (id, note, timestamp, left, top, zindex) VALUES (?, ?, ?, ?, ?, ?)", [note.id, note.text, note.timestamp, note.left, note.top, note.zIndex]); }); }, onMouseDown: function(e) { captured = this; this.startX = e.clientX - this.note.offsetLeft; this.startY = e.clientY - this.note.offsetTop; this.zIndex = ++highestZ; var self = this; if (!("mouseMoveHandler" in this)) { this.mouseMoveHandler = function(e) { return self.onMouseMove(e) } this.mouseUpHandler = function(e) { return self.onMouseUp(e) } } document.addEventListener('mousemove', this.mouseMoveHandler, true); document.addEventListener('mouseup', this.mouseUpHandler, true); return false; }, onMouseMove: function(e) { if (this != captured) return true; this.left = e.clientX - this.startX + 'px'; this.top = e.clientY - this.startY + 'px'; return false; }, onMouseUp: function(e) { document.removeEventListener('mousemove', this.mouseMoveHandler, true); document.removeEventListener('mouseup', this.mouseUpHandler, true); this.save(); return false; }, onNoteClick: function(e) { this.editField.focus(); getSelection().collapseToEnd(); }, onKeyUp: function() { this.dirty = true; this.saveSoon(); }, }
Well, that was a handful. Let's start from the very beginning:
Note.prototype = {
This line declares that we're going to be attaching methods to the Note object that we defined earlier.
get id() { if (!("_id" in this)) this._id = 0; return this._id; }, set id(x) { this._id = x; }, get text() { return this.editField.innerHTML; }, set text(x) { this.editField.innerHTML = x; }, get timestamp() { if (!("_timestamp" in this)) this._timestamp = 0; return this._timestamp; }, set timestamp(x) { if (this._timestamp == x) return; this._timestamp = x; var date = new Date(); date.setTime(parseFloat(x)); this.lastModified.textContent = modifiedString(date); }, get left() { return this.note.style.left; }, set left(x) { this.note.style.left = x; }, get top() { return this.note.style.top; }, set top(x) { this.note.style.top = x; }, get zIndex() { return this.note.style.zIndex; }, set zIndex(x) { this.note.style.zIndex = x; },
These are called "getter" and "setter" methods. Here we're defining methods for id, text, timestamp, left, top, and zIndex. These functions allow us to externally access and write to a Note's internal data by using commands like note.left, note.text, etc. Some of these are visible properties of the Note, such as left and top. Getting the left value of a note returns the x-coordinate of its left edge in the browser window. Setting the left value will actually move the note to the specified x-coordinate. Similarly, the setting the text value changes the text that is displayed in the note, while getting the text value retrieves the text that the user has typed into the note.
close: function(event) { this.cancelPendingSave(); var note = this; db.transaction(function(tx) { tx.executeSql("DELETE FROM WebKitStickyNotes WHERE id = ?", [note.id]); }); var duration = event.shiftKey ? 2 : .25; this.note.style.webkitTransition = '-webkit-transform ' + duration + 's ease-in, opacity ' + duration + 's ease-in'; this.note.offsetTop; // Force style recalc this.note.style.webkitTransformOrigin = "0 0"; this.note.style.webkitTransform = 'skew(30deg, 0deg) scale(0)'; this.note.style.opacity = '0'; var self = this; setTimeout(function() { document.body.removeChild(self.note) }, duration * 1000); },
This function is called when a user closes a note. Here we've got a database transaction:
db.transaction(function(tx) { tx.executeSql("DELETE FROM WebKitStickyNotes WHERE id = ?", [note.id]); });
Here we're defining an anonymous function, and passing this function in to the db.transaction method. The db.transaction method will then use the function we've passed in as it manages and executes the database transaction. The function defines a simple sql statement that deletes a note from the WebKitStickyNotes table in the database. "What? Where did that table come from?". This table actually gets created in the loaded() function we'll take a look at later. :)
saveSoon: function() { this.cancelPendingSave(); var self = this; this._saveTimer = setTimeout(function() { self.save() }, 200); }, cancelPendingSave: function() { if (!("_saveTimer" in this)) return; clearTimeout(this._saveTimer); delete this._saveTimer; },
These two functions manage the saving of the notes. The first function gets called every time the user types a character into a note. The cancelPendingSave() function (which is also called every time a user types a character) resets a 200 millisecond timer on the save action. The net function of these two methods is to call the save() function to save the note to the database if the user hasn't pressed a key within the last 200 milliseconds.
save: function() { this.cancelPendingSave(); if ("dirty" in this) { this.timestamp = new Date().getTime(); delete this.dirty; } var note = this; db.transaction(function (tx) { tx.executeSql("UPDATE WebKitStickyNotes SET note = ?, timestamp = ?, left = ?, top = ?, zindex = ? WHERE id = ?", [note.text, note.timestamp, note.left, note.top, note.zIndex, note.id]); }); },
This function saves a note's contents to the database. If dirty exists, the timestamp of the note gets updated before the save. The db function then UPDATEs the note's record in the database. The next function creates a record for new notes.
saveAsNew: function() { this.timestamp = new Date().getTime(); var note = this; db.transaction(function (tx) { tx.executeSql("INSERT INTO WebKitStickyNotes (id, note, timestamp, left, top, zindex) VALUES (?, ?, ?, ?, ?, ?)", [note.id, note.text, note.timestamp, note.left, note.top, note.zIndex]); }); },
This function creates a new note record in the table WebKitStickyNotes and is executed whenever the "New Note" button is clicked. This means that any note you interact with already has a record in the database, so when the save() function is called, the record only needs to be updated.
onMouseDown: function(e) { captured = this; this.startX = e.clientX - this.note.offsetLeft; this.startY = e.clientY - this.note.offsetTop; this.zIndex = ++highestZ; var self = this; if (!("mouseMoveHandler" in this)) { this.mouseMoveHandler = function(e) { return self.onMouseMove(e) } this.mouseUpHandler = function(e) { return self.onMouseUp(e) } } document.addEventListener('mousemove', this.mouseMoveHandler, true); document.addEventListener('mouseup', this.mouseUpHandler, true); return false; }, onMouseMove: function(e) { if (this != captured) return true; this.left = e.clientX - this.startX + 'px'; this.top = e.clientY - this.startY + 'px'; return false; }, onMouseUp: function(e) { document.removeEventListener('mousemove', this.mouseMoveHandler, true); document.removeEventListener('mouseup', this.mouseUpHandler, true); this.save(); return false; },
These functions manage the dragging of a note around on the screen. Whether the drag and drop interaction in webOS works the same way remains to be seen, but webOS does support dragging and dropping.
onNoteClick: function(e) { this.editField.focus(); getSelection().collapseToEnd(); },
This function is executed whenever a user clicks a note. It brings the clicked note into focus and places the cursor at the end of the text.
onKeyUp: function() { this.dirty = true; this.saveSoon(); },
This function is executed every time a user presses a key.
That covers the Note prototype. In the next section, we'll go through the remaining functions.
We've almost gone through the entire code of this simple HTML5 note application. Just a few more functions and then we can speculate on how this application might be ported to webOS!
function loaded() { db.transaction(function(tx) { tx.executeSql("SELECT COUNT(*) FROM WebkitStickyNotes", [], function(result) { loadNotes(); }, function(tx, error) { tx.executeSql("CREATE TABLE WebKitStickyNotes (id REAL UNIQUE, note TEXT, timestamp REAL, left TEXT, top TEXT, zindex REAL)", [], function(result) { loadNotes(); }); }); }); }
This function calls db.transaction with two parameters: a function that gets the number of notes in the WebkitStickyNotes table, and a function that creates the WebStickyNotes table. Why would you create the table after you count the number of notes in it? The way the db.transaction function works is that the second parameter is only executed if the first one fails. If the table WebkitStickyNotes doesn't exist, the function will fail, causing the second function, which sets up the table, to be run.
function loadNotes() { db.transaction(function(tx) { tx.executeSql("SELECT id, note, timestamp, left, top, zindex FROM WebKitStickyNotes", [], function(tx, result) { for (var i = 0; i highestId) highestId = row['id']; if (row['zindex'] > highestZ) highestZ = row['zindex']; } if (!result.rows.length) newNote(); }, function(tx, error) { alert('Failed to retrieve notes from database - ' + error.message); return; }); }); }
This function follows the same structure as the previous one. The first function loads the notes from the database and creates a new note object on screen for each note record that is retrieved. If this function fails, the second function is run, which simply displays an alert to the user along with the error message.
function modifiedString(date) { return 'Last Modified: ' + date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate() + ' ' + date.getHours() + ':' + date.getMinutes() + ':' + date.getSeconds(); }
This function returns current date and time and is called whenever the user updates the note.
function newNote() { var note = new Note(); note.id = ++highestId; note.timestamp = new Date().getTime(); note.left = Math.round(Math.random() * 400) + 'px'; note.top = Math.round(Math.random() * 500) + 'px'; note.zIndex = ++highestZ; note.saveAsNew(); }
This function gets called whenever the user clicks the "New Note" button. It sets up a new note by:
- assigning it the next available ID
- setting its timestamp
- setting the left position of the note to a random location between 0 and 400 pixels
- setting the top position of the note to a random location between 0 and 500 pixels
- setting the zIndex of the note so that the note appears on top of existing notes
- creating a new note record by calling saveAsNew().
addEventListener('load', loaded, false);
This adds a listener to the page that executes the loaded() function when the page finishes loading. If you recall, the loaded() function loads existing notes, or creates the WebKitStickyNotes table in the database if it doesn't exist.
<p>This page demonstrates the use of the <a href="https://www.whatwg.org/specs/web-apps/current-work/multipage/section-sql.html">HTML 5 Client-side Database Storage API</a>. Any notes you create will be saved in a database on your local hard drive, and will be reloaded from that database the next time you visit this page. To try it out, use a <a href="https://nightly.webkit.org/">WebKit Nightly Build</a>.</p> <button onClick="newNote()">New Note</button>
And there you have it! A complete, functioning web application that uses the local database functionality built in to HTML5.
Making a true webOS Application
As-is, this is a web application that could potentially be accessed and used through the Pre's built-in web browser. But what if we wanted to turn this into a native Pre application that could be launched just as you would launch the Calendar or Calculator applications?
As of February 22, 2009, Palm has not released enough information to describe exactly how this would be accomplished. But they have provided a few hints in Chapter 1 of Palm webOS from O'Reilly:
- We would need to set up a stage, which is a "declarative HTML structure like a conventional HTML window or browser tab". Perhaps this would simply be the code that currently appears between the and tags of this example. But more likely there will need to be some additional setup required.
- We would also need to set up a scene within the stage, which is a "sub-view" of a stage. We would then need to activate the scene so that it is visible to the user.
- We would have to create a set of files, including:
- an appinfo.json file that provides information webOS needs to install and load the application
- an index.html file
- an icon.png file which will represent the application in the launcher
- an app folder with a directory structure that contains assistants and views
As additional details become available, this section will be updated with explicit instructions on how you would set this application up as a webOS application using the Mojo framework. Stay tuned!
Note: I plan to go back through this tutorial in the near future to provide some additional detail, clarification, and useful references to the HTML5 draft spec and other resources. I'm just a bit swamped at the moment getting everything else up and running. But please leave comments indicating sections where you feel additional clarification would be helpful and I'll be sure to address those first. :)
--Ken
Jacob Christian Munch-Andersen makes this comment
Friday, 19 June 2009
Nolen makes this comment
Thursday, 25 June 2009
Jeffrey Hyer makes this comment
Thursday, 02 July 2009
The above comments are clearly the product of uneducated users. If they understood HTML5 db's and the webOS they would know that javascript (that so called "unrelated code") does everything as far as the database interaction and user interaction goes and therefore is, not only related, but necessary to make proper use of the database structure and apply it to both the webOS platform and the web in general.
Kudos to Mr. Ken Young for a great tutorial, I have learned many new things from this article and appreciate the time he took to write it. Also, I apologize for the aforementioned flaming of users, though quite rude I deemed it necessary in order to maintain my sanity and vent some frustration...
okeribok makes this comment
Wednesday, 08 July 2009
bkuberek makes this comment
Friday, 10 July 2009
ramesh makes this comment
Friday, 24 July 2009
is there any import files such as jar or any files for using html5 Palm WebOS. ?
where will use the above code
var db;
try {
if (window.openDatabase) {
db = openDatabase("NoteTest", "1.0", "HTML5 Database API example", 200000);
if (!db)
alert("Failed to open the database on disk. This is probably because the version was bad or there is not enough space left in this domain's quota");
} else
alert("Couldn't open the database. Please try with a WebKit nightly with this feature enabled");
} catch(err) { }
html file or js file..?
how to call and access database connection ..?
is there any sample application plz send me some samples...
comullen makes this comment
Tuesday, 08 September 2009
Rodolfo Gonzalez makes this comment
Wednesday, 13 January 2010
is possible to access the temporally database and copy to other place?
thanks
mike makes this comment
Friday, 05 February 2010
you left out just a couple of lines that a beginner might not see that you left out. By the way the code at the beginner was placed it was not clear that you need this between the styles and the javascript at the beginning:
and also left this out at the end before the paragraph:
plus 2 more lines:
I would like to know what the tx is that is being passed in this part:
db.transaction(function(tx) {
it is not clear from the tutorial.
also explain what this line means:
if (!("_saveTimer" in this))
it looks for a method called _saveTimer attached to self ( or this )
everything else made sense. And what does this mean:
++highestZ;
I would guess that it does the same as highestZ++; I just never knew you could reverse it.
Thanks for the great lesson on html5!
mike makes this comment
Friday, 05 February 2010
I really liked this tutorial. It helped me understand how to make a database work. I have a couple recomendations for the author:
you left out just a couple of lines that a beginner might not see that you left out. By the way the code at the beginner was placed it was not clear that you need this between the styles and the javascript at the beginning:
and also left this out at the end before the paragraph:
plus 2 more lines:
I would like to know what the tx is that is being passed in this part:
db.transaction(function(tx) {
it is not clear from the tutorial.
also explain what this line means:
if (!("_saveTimer" in this))
it looks for a method called _saveTimer attached to self ( or this )
everything else made sense. And what does this mean:
++highestZ;
I would guess that it does the same as highestZ++; I just never knew you could reverse it.
Thanks for the great lesson on html5!
mike makes this comment
Friday, 05 February 2010
I really liked this tutorial. It helped me understand how to make a database work. I have a couple recomendations for the author:
you left out just a couple of lines that a beginner might not see that you left out. By the way the code at the beginner was placed it was not clear that you need this between the styles and the javascript at the beginning:
and also left this out at the end before the paragraph:
plus 2 more lines:
I would like to know what the tx is that is being passed in this part:
db.transaction(function(tx) {
it is not clear from the tutorial.
mike makes this comment
Friday, 05 February 2010
I really liked this tutorial. It helped me understand how to make a database work. I have a couple recomendations for the author:
you left out just a couple of lines that a beginner might not see that you left out. By the way the code at the beginner was placed it was not clear that you need this between the styles and the javascript at the beginning:
end style tag (no angle bracket because the synax is not allowed here in comments)
script tag
and also left this out at the end before the paragraph:
script tag
end head tag
body tag
plus 2 more lines at the very end of code:
end body tag
end html tag
I would like to know what the tx is that is being passed in this part:
db.transaction(function(tx) {
it is not clear from the tutorial.
and one more makes this comment
Friday, 05 February 2010
also explain what this line means:
if (!("_saveTimer" in this))
it looks for a method called _saveTimer attached to self ( or this )
everything else made sense. And what does this mean:
++highestZ;
I would guess that it does the same as highestZ++; I just never knew you could reverse it.
Thanks for the great lesson on html5!
Ken makes this comment
Monday, 08 February 2010
I've been way behind updating the site because of other things going on (e.g. getting a job that pays the bills!), but I'm hoping to get back to it soon!
Brent makes this comment
Tuesday, 11 May 2010
Michael makes this comment
Friday, 23 July 2010
Lately I've felt as if I am closing in on the center, slowly but surely. Your tutorial has given me a hefty nudge in that direction and the broad brush was exactly what I needed.
You cleared up a number of those 'I think I know, sorta, kinda...' things that have been bugging me.
Sincerely,
Michael
Mitur Binesderti makes this comment
Wednesday, 02 February 2011
Chris Leong makes this comment
Friday, 27 May 2011