Row's ID is not transferring

I am trying to delete a row in a database via external JS, but don’t think the syntax is right. What I have right now is my best guess as to what it should be.

The database rows show an ID, filename, and title (context: a bookmarked or favorite page), and when a query is made, it shows two buttons side by side for each row; the left button shows the title and when tapped goes to the page, while the right button is used to remove the row. This code successfully generates the buttons (via innerHtml):

function querySuccess(tx, results) {
var len = results.rows.length;
console.log("BOOKMARKS table: " + len + " rows found.");
	for (var i=0; i<len; i++) {
	[COLOR="#FF0000"]rowId = results.rows.item(i).id;[/COLOR]
	document.getElementById("output").innerHTML +="<div class='row'>" +
	"<a href='" + results.rows.item(i).filename + "'><input type='button' class='buttonBM' value='" + results.rows.item(i).title + "'></a></div>" +
	"<div class='buttonRemove'><input type='button' class='buttonEdit' value='Remove' onclick='removeRow('" + [COLOR="#FF0000"]rowId[/COLOR] + "')'></a></div>";
	} 
}

What I need to do is take the rowId and use it to delete the correct row in the DB, and I do it like this:

function removeRow() {
db = window.openDatabase("Database", "1.0", "Bookmarks", 200000);
[COLOR="#FF0000"]var rowId;[/COLOR]
db.transaction(function(tx) {
	tx.executeSql("delete from BOOKMARKS where id='" + [COLOR="#FF0000"]rowId[/COLOR] + "'");
	}, errorCB, successCB()); alert('Bookmark removed.');
}

However, it fails to delete the row. Google Tools gives the error as “Uncaught SyntaxError: Unexpected token }” which is on the external HTML page (which could be any page in the app containing the Add Bookmark button). The error points to the <HTML> line, so it could be anything.

Below is the above code in context:

// Add row via button on external page

function insertRow() {
db = window.openDatabase("Database", "1.0", "Bookmarks", 200000);
db.transaction(function(tx) {
	var id, filename = window.location.pathname, title = document.title;
	tx.executeSql("insert into BOOKMARKS(id, filename, title) values(?,?,?)", [id, filename, title]);
	}, errorCB, successCB()); alert('Bookmark added.');
}

// Display DB results (onLoad)

function queryDB(tx) {
tx.executeSql("SELECT * FROM BOOKMARKS", [], querySuccess, errorCB);
}

function querySuccess(tx, results) {
var len = results.rows.length;
console.log("BOOKMARKS table: " + len + " rows found.");
	for (var i=0; i<len; i++) {
	rowId = results.rows.item(i).id;
	document.getElementById("output").innerHTML +="<div class='row'>" +
	"<a href='" + results.rows.item(i).filename + "'><input type='button' class='buttonBM' value='" + results.rows.item(i).title + "'></a></div>" +
	"<div class='buttonRemove'><input type='button' class='buttonEdit' value='Remove' onclick='removeRow('" + rowId + "')'></a></div>";
	} 
}

// Remove row via button on this page

function removeRow() {
db = window.openDatabase("Database", "1.0", "Bookmarks", 200000);
var rowId;
db.transaction(function(tx) {
	tx.executeSql("delete from BOOKMARKS where id='" + rowId + "'");
	}, errorCB, successCB()); alert('Bookmark removed.');
}

// Transaction error callback (corrected per Sitepoint.com post)

function errorCB(tx, err) {
   alert('Error processing SQL: ' + err);
}

Of course, the Google error could be pointing to an entirely different part of the two pages!

I think that the problem is in some other part of the code. There aren’t any unexpected }'s in that code. Not even after using jslint to help tidy it up.
The only other issue there is that successCB() isn’t correct, it should be just a reference to a function instead, like with the errorCB function.
The successCB one is optional though, so you can remove the function and the reference to it if you don’t want to use it.


// Transaction error callback (corrected per Sitepoint.com post)

function errorCB(tx, err) {
    alert('Error processing SQL: ' + err);
}

function successCB() {
}

// Add row via button on external page

function insertRow() {
    var db = window.openDatabase("Database", "1.0", "Bookmarks", 200000);
    db.transaction(function (tx) {
        var id, filename = window.location.pathname,
            title = document.title;
        tx.executeSql("insert into BOOKMARKS(id, filename, title) values(?,?,?)", [id, filename, title]);
    }, errorCB, successCB);
    alert('Bookmark added.');
}

function querySuccess(tx, results) {
    var len = results.rows.length,
        i,
        rowId;
    console.log("BOOKMARKS table: " + len + " rows found.");
    for (i = 0; i < len; i += 1) {
        rowId = results.rows.item(i).id;
        document.getElementById("output").innerHTML += "<div class='row'>" + "<a href='" + results.rows.item(i).filename + "'><input type='button' class='buttonBM' value='" + results.rows.item(i).title + "'></a></div>" + "<div class='buttonRemove'><input type='button' class='buttonEdit' value='Remove' onclick='removeRow('" + rowId + "')'></a></div>";
    }
}

// Display DB results (onLoad)

function queryDB(tx) {
    tx.executeSql("SELECT * FROM BOOKMARKS", [], querySuccess, errorCB);
}

// Remove row via button on this page

function removeRow() {
    var db = window.openDatabase("Database", "1.0", "Bookmarks", 200000),
        rowId;
    db.transaction(function (tx) {
        tx.executeSql("delete from BOOKMARKS where id='" + rowId + "'");
    }, errorCB, successCB);
    alert('Bookmark removed.');
}

Thanks, Paul. Here is the HTML the error code points to. The external JS’ innerHtml is written to the span=output part:

<!DOCTYPE html>
<html>

==>  Uncaught SyntaxError: Unexpected token } <==

<head>
<title>Bookmarks 3 html</title>

<script type="text/javascript" charset="utf-8" src="cordova-1.7.0.js"></script>
<script type="text/javascript" charset="utf-8" src="original4.js"></script>
<script>

</script>

<style type="text/css">
body {margin:0; padding:0; background-color: #aaa;}
div#wrapper{width:100%;padding:.5em;}
div.row{font-family:Helvetica, Arial, sans-serif; color: #333; margin:0;padding:0; }


/* input */
input[type="button"] {
background-color:#fff;font-family:Helvetica, Arial, sans-serif; color: #333; text-align:center;margin:0;padding:1em;float:left;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
-webkit-box-shadow: 5px 5px 4px 0px #888; /* Safari 3-4, iOS 4.0.2 &#8211; 4.2, Android 2.3+ */
box-shadow: 5px 5px 4px 0px #888; /* Opera 10.5, IE9, Firefox 4+, Chrome 6+, iOS 5 */
border:1px #666 solid;
}
input[type="button"]:hover {background-color:#ddd;}
.buttonBM { width:70%; }
.buttonEdit { width:28%; }
.addBM {width:100%; float:left;clear:both;margin:1em;}
</style>

</head>
<body onload="onDeviceReady()">


<div id="wrapper"><span id="output"></span></div>
<br>

<a href="original3.html"><p style="padding: .5em;line-height:4em;">Back</p></a>
</body>
</html>

Your version of the code places my functions in a different order. Can you explain why?

Because it’s not good to use a function before it has been defined. When using a function statement you can mostly get away with it because functions within the same scope are defined first, after which scripting it processed. But if you ever use function expressions then the problem becomes quite obvious.


someFunc('Did this run?'); // nope

var someFunc = function (str) {
    alert(str);
}

So declare what you need (the function in this case) before making use of it. That helps to prevent all sorts of problems.


var someFunc = function (str) {
    alert(str);
}

someFunc('Did this run?'); // yes

Because the ordering does affect function statements, and function statements are used interchangably with function expressions, it’s best if the one ordering rule is applied across both of them. This helps to simplify things, and allows the code to more easily be improved upon at later stages as well.

Remove everything you can until you are left with the core code that still causes the problem. How much of it can you remove while still retaining the same problem?

I did the following one at a time, cutting, saving, testing, then replacing to remove something else:

I removed the entire CSS style section. Still errors.
I removed the Back link, still errors.
Removed <HTML>. Still errors.
Removed <!DOCTYPE html>. Still errors.
Removed empty <script>tags. Still errors.

That leaves the innerHtml span id = output line. Can’t take that out, or there are no buttons to press! I suppose, in conclusion, the error is happening in the innerHtml output?

Currently we’re not focused on having a working system. Right now we’re focused on figuring out where that error with the brace is coming from. Keep on chopping things out.

If I remove the innerHtml line, then there is no content on the page, so I get the error,

Uncaught TypeError: Cannot read property ‘innerHTML’ of null

The error only occurs after I click on one of the Remove buttons, which is rendered inside the innerHtml.

I separated the css and js content into their own files.

HTML:

<!DOCTYPE html>
<html>
<head>
<title>Bookmarks 3 html</title>

<script type=“text/javascript” charset=“utf-8” src=“cordova-1.7.0.js”></script>
<script type=“text/javascript” charset=“utf-8” src=“original4.js”></script>
<link rel=“stylesheet” type=“text/css” media=“screen” href=“bookmarks3.css”>

<style type=“text/css”>

</style>

</head>
<body onload=“onDeviceReady()”>

<div id=“wrapper”><span id=“output”></span></div>

<a href=“original3.html”><p style=“padding: .5em;”>Back</p></a>
</body>
</html>

CSS:

body {margin:0; padding:0; background-color: #aaa;}
div#wrapper{width:100%;padding:.5em;}
div.row{font-family:Helvetica, Arial, sans-serif; color: #333; margin:0;padding:0; }
div.bookmark {margin:.5em;}
p{text-align:left;}
p.title {font-weight:bold;font-size:1em;text-align:center; color:#000}
a {color:#666}

/* input /
input[type=“button”] {
background-color:#fff;font-family:Helvetica, Arial, sans-serif; color: #333; text-align:left;margin:0;padding:1em;float:left;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
-webkit-box-shadow: 5px 5px 4px 0px #888; /
Safari 3-4, iOS 4.0.2 – 4.2, Android 2.3+ /
box-shadow: 5px 5px 4px 0px #888; /
Opera 10.5, IE9, Firefox 4+, Chrome 6+, iOS 5 */
border:1px #666 solid;
}
.buttonBM { width:67%; }
.buttonEdit { width:33%; }
.addBM {width:100%; float:left;clear:both;margin:1em;}

JS:

/* Helpful info:
base: http://docs.phonegap.com/en/2.0.0/cordova_storage_storage.md.html#Storage
modifications: http://tv.adobe.com/watch/adc-presents/phonegap-storage-api/eng/
*/

// Wait for Cordova to load
document.addEventListener(“deviceready”, onDeviceReady, false);

var db;
function onDeviceReady() {
db = window.openDatabase(“Database”, “1.0”, “Bookmarks”, 200000);
db.transaction(populateDB, errorCB, successCB);
}

// Transaction error callback (corrected per Sitepoint.com post)

function errorCB(tx, err) {
alert('Error processing SQL: ’ + err);
}

// Transaction success callback

function successCB() {
var db = window.openDatabase(“Database”, “1.0”, “Bookmarks”, 200000);
db.transaction(queryDB, errorCB);
}

// Display the rows on this page (onLoad)

function querySuccess(tx, results) {
var len = results.rows.length;
console.log(“BOOKMARKS table: " + len + " rows found.”);
for (var i=0; i<len; i++) {
rowId = results.rows.item(i).id;
document.getElementById(“output”).innerHTML +=“<div class=‘row’>” +
“<a href='” + results.rows.item(i).filename + “‘><input type=‘button’ class=‘buttonBM’ value=’” + results.rows.item(i).title + “'></a></div>” +
“<div class=‘buttonRemove’><input type=‘button’ class=‘buttonEdit’ value=‘Remove’ onclick=‘removeRow(’” + rowId + “‘)’></a></div>”;
} document.getElementById(“output”).innerHTML +=“<div class=‘row’><p style=‘line-height:2em;’>You have " + len + " bookmarks.</p></div>”;
}

// Query the DB

function queryDB(tx) {
tx.executeSql(“SELECT * FROM BOOKMARKS”, , querySuccess, errorCB);
}

// Remove row via button on this page

function removeRow() {
db = window.openDatabase(“Database”, “1.0”, “Bookmarks”, 200000);
var rowId;
db.transaction(function(tx) {
tx.executeSql(“delete from BOOKMARKS where id='” + rowId + “'”);
}, errorCB, successCB()); alert(‘Bookmark removed.’);
}

// Add row via button on external page

function insertRow() {
db = window.openDatabase(“Database”, “1.0”, “Bookmarks”, 200000);
db.transaction(function(tx) {
var id, filename = window.location.pathname, title = document.title;
tx.executeSql(“insert into BOOKMARKS(id, filename, title) values(?,?,?)”, [id, filename, title]);
}, errorCB, successCB()); alert(‘Bookmark added.’);
}

// Create the database

function populateDB(tx) {
tx.executeSql(‘CREATE TABLE IF NOT EXISTS BOOKMARKS (id INTEGER NOT NULL, filename TEXT NOT NULL, title TEXT NOT NULL)’);
}

// Cordova is ready

function onDeviceReady() {
var db = window.openDatabase(“Database”, “1.0”, “Bookmarks”, 200000);
db.transaction(populateDB, errorCB, successCB);
}

In Google Tools, I clicked on the Elements tab and examined the innerHtml content as output to the screen. I think I found the problem.

The innerHtml line has (copying and pasting):

"<div class='buttonRemove'><input type='button' class='buttonEdit' value='Remove' onclick='removeRow('" + rowId + "')'></a></div>";

The Elements tab has (copying and pasting):

<input type="button" class="buttonEdit" value="Remove" onclick="removeRow(" undefined')'="">

The rowId is not rendering properly.

Also, the copy/paste from google Tools Elements tab shows more than what I see on the Elements tab on screen. On screen, I don’t see undefined’)'=“”> but undefined’)'>

It looks like we need to figure out why the variable is not being populated. On a separate HTML page, which contains the button to execute the code to enter a new row, the code used is:

<div class="bookmark"><input type="button" class="addBM" value="Add Bookmark" onclick="insertRow('100', window.location.pathname, document.title)"></div>

Is the syntax wrong? The pathname and title are all appearing correctly. Would the following function understand the order of the three variables, and understand the first one is id?:

function insertRow() {
db = window.openDatabase("Database", "1.0", "Bookmarks", 200000);
db.transaction(function(tx) {
	var id, filename = window.location.pathname, title = document.title;
	tx.executeSql("insert into BOOKMARKS(id, filename, title) values(?,?,?)", [id, filename, title]);
	}, errorCB, successCB()); alert('Bookmark added.');
}

You know, since I am using an ID number to reference the row to delete, but it isn’t showing up, BUT the title is showing up, I might as well remove the ID completely and substitute with the title, which IS showing up. So far, that isn’t working either, for the output is generated as

<input type="button" class="buttonEdit" value="Remove" onclick="removeRow(" original="" 3="" html')'>

even though the JS line in innerHtml is:

"<div class='buttonRemove'><input type='button' class='buttonEdit' value='Remove' onclick='removeRow('"+results.rows.item(i).title+"')'></div>";

The quotes are still not right, and the generated output adds a space before the row title and quotes within the title. (I also tried it with the filename, but it gave a long pathname rather than just the filename. I don’t see a window.location code that retrieves only the filename.)

You know, since I am using an ID number to reference the row to delete, but it isn’t showing up, BUT the title is showing up, I might as well remove the ID completely and substitute with the title, which IS showing up. So far, that isn’t working either, for the output is generated as

<input type=“button” class=“buttonEdit” value=“Remove” onclick=“removeRow(” original=“” 3=“” html’)'>

even though the JS line in innerHtml is:

“<div class=‘buttonRemove’><input type=‘button’ class=‘buttonEdit’ value=‘Remove’ onclick=‘removeRow(’”+results.rows.item(i).title+“‘)’></div>”;

Now I’ve got the removeRow to show the title without the =“” within the words, and the database is showing correct entries. But I am still getting the “unexpected token }” error.

From the code that you had in post #9 I’m seeing that it should just be the quotes that are causing the issue.

This line seems to be the problem:


"<div class='buttonRemove'><input type='button' class='buttonEdit' value='Remove' onclick='removeRow('" + rowId + "')'>[color="red"]</a>[/color]</div>";

By the way - the </a> from the javascript code part shouldn’t be there either.

It results in this HTML code:


<div class="buttonRemove"><input type="button" class="buttonEdit" value="Remove" onclick="removeRow([color="red"]"[/color] row1[color="red"]')'=""[/color]></div>

See how the quotes are all messed up?

What you need there instead is: onclick=“removeRow(‘row1’)”

Notice though that even when we start the javascript string with single quotes, that by the time we get through the HTML double quotes and once more to the single quotes required for the removeRow part, that we’re going to end up closing the string instead. So, for those innermost single quotes we’ll need to escape them as \’ instead.


'<div class="buttonRemove"><input type="button" class="buttonEdit" value="Remove" onclick="removeRow(\\'' + rowId + '\\')"></div>';

That seems to work now.

Wow, this is very good! Clicking on Remove now fires the removeRow function. Unfortunately, the screen is refreshing and inserting all the previous rows under the table, so the table is showing up multiplied by the number of times one clicks on Remove. We need to terminate the script so this doesn’t happen. However, I’m not sure that exit(); is the approved way to do this.

Thanks again, Paul!

Actually, I click on Remove, and see the “Bookmark removed” alert, then the row is NOT REMOVED but repeated below the “You have X Bookmarks” message.

If I go back a page, then return to the bookmarks page, the bookmarks I removed are gone.

For some reason, when I comment out the successCB as you suggest, the bookmarks don’t appear on the page (the area is just blank, showing the background). Uncommenting the lines causes them to appear again.

Here is the current JS code:

/* Helpful info:
base: http://docs.phonegap.com/en/2.0.0/cordova_storage_storage.md.html#Storage
modifications: http://tv.adobe.com/watch/adc-presents/phonegap-storage-api/eng/
*/

// Wait for Cordova to load
document.addEventListener("deviceready", onDeviceReady, false);

var db;
function onDeviceReady() {
db = window.openDatabase("Database", "1.0", "BOOKMARKS3", 200000);
db.transaction(populateDB, errorCB, successCB);
}

// Create the database

function populateDB(tx) {
tx.executeSql('CREATE TABLE IF NOT EXISTS BOOKMARKS3 (id INTEGER NOT NULL, filename TEXT NOT NULL, title TEXT NOT NULL)');
}

// 1. Transaction error callback (corrected per Sitepoint.com post)

function errorCB(tx, err) {
   alert('Error processing SQL: ' + err);
}

// 2. Transaction success callback

function successCB() {
var db = window.openDatabase("Database", "1.0", "BOOKMARKS3", 200000);
db.transaction(queryDB, errorCB);
}

// 3. Add row via button on external page

function insertRow() {
db = window.openDatabase("Database", "1.0", "BOOKMARKS3", 200000);
db.transaction(function(tx) {
	var id, filename = window.location.pathname, title = document.title;
	tx.executeSql("insert into BOOKMARKS3(id, filename, title) values(?,?,?)", [id, filename, title]);
	}, errorCB, successCB()); alert('Bookmark added.');
}

// 4. Display the rows on this page (onLoad)

function querySuccess(tx, results) {
var len = results.rows.length;
console.log("BOOKMARKS3 table: " + len + " rows found.");
	for (var i=0; i<len; i++) {
	rowId = results.rows.item(i).id;
	document.getElementById("output").innerHTML +="<div class='row'>" +
	"<a href='" + results.rows.item(i).filename + "'><input type='button' class='buttonBM' value='" + results.rows.item(i).title + "'></a></div>" +
	'<div class="buttonRemove"><input type="button" class="buttonEdit" value="Remove" onclick="removeRow(\\'' + rowId + '\\')"></div>';
	} document.getElementById("output").innerHTML +="<div class='row'><p style='line-height:2em;'>You have " + len + " BOOKMARKS3.</p></div>";
}

// 5. Query the DB

function queryDB(tx) {
tx.executeSql("SELECT * FROM BOOKMARKS3", [], querySuccess, errorCB);
}

// 6. Remove row via button on this page

function removeRow() {
db = window.openDatabase("Database", "1.0", "BOOKMARKS3", 200000);
var rowId;
db.transaction(function(tx) {
	tx.executeSql("delete from BOOKMARKS3 where id='" + rowId + "'");
	}, errorCB, successCB()); alert('Bookmark removed.');
} exit;

// Cordova is ready

function onDeviceReady() {
var db = window.openDatabase("Database", "1.0", "BOOKMARKS3", 200000);
db.transaction(populateDB, errorCB, successCB);
}

I solved the problem by replacing the successCB() with a doNothing() function to the removeRow(). Now it does not add a new row, and refreshes the page to show the new status:

function removeRow() {
db = window.openDatabase("Database", "1.0", "BOOKMARKS3", 200000);
var rowId;
db.transaction(function(tx) {
	tx.executeSql("delete from BOOKMARKS3 where id='" + rowId + "'");
	}, errorCB, doNothing()); alert('Bookmark removed.');
}

function doNothing() {
window.location.href=window.location.href;
}

Is this syntax OK, or is this unconventional?

You know, when I press the button to add that page’s title and filename to the database, it works because of this in the page’s head:

<script type=“text/javascript” charset=“utf-8” src=“original4.js”></script>

However, that means ALL the JS functions on that page try to execute whenever I first open the page AND after I click on the Add Bookmark button. Obviously, I’m going to turn off the debugging alerts when I’m done, but is there a more “correct” way of doing this? The bookmarking code will appear on over 200 pages in a book app I’m working on.