Reputation: 183
With IndexedDB, I can't seem to create a second transaction to my objectstore (just a list of ToDo's) once I've already created a transaction to read and display the data. I don't get an error on the line that tries to create the second transaction, but nothing happens and code stops execution.
This part that retrieves and displays my data runs fine:
var trans;
var ostore;
var db;
var reqdb = window.indexedDB.open("ToDoApp", 2);
reqdb.onupgradeneeded = function (event) {
console.log("running onupgradeneeded");
var myDB = event.target.result;
if (!myDB.objectStoreNames.contains("todos")) {
myDB.createObjectStore("todos", { keyPath: "ToDoTitle" });
}
};
reqdb.onsuccess = function (event) {
db = event.target.result;
trans = db.transaction(["todos"], "readonly");
ostore = trans.objectStore("todos");
var req = ostore.get("TEST IDB 2");
$("#btnSave").click(function () {
UpdateToDo();
});
req.onerror = function (event) {
alert("error");
};
req.onsuccess = function (event) {
$("#ToDoTitle").val(req.result.ToDoTitle);
};
};
This gets and displays things just fine. But notice the UpdateToDo() function that gets set with the onclick event so I can actually UPDATE my data.
function UpdateToDo(event) {
alert("1");
var newtransaction = db.transaction(["todos"], "readwrite");
alert("2");
var newstore = newtransaction.objectStore("todos");
newstore.openCursor().onsuccess = function (event) {
const cursor = event.target.result;
if (cursor) {
if (cursor.value.ToDoTitle == 'TEST IDB 2') {
const updateData = cursor.value;
updateData.ToDoCategory = 1; // hard coding for now
var requpdate = cursor.update(updateData);
requpdate.onsuccess = function () {
console.log('Updated');
};
requpdate.onerror = function () {
console.log('Error');
}
};
cursor.continue();
} else {
console.log('Cursor error.');
}
};
}
This first alert fires, but the second one doesn't. I assumed that because the first callback that create the first transaction was returned, that transaction was closed, but it still appears to be blocking me from creating this transaction. If I take out the first transaction COMPLETELY, the second transaction gets created and the second alert runs... then from there I can create a cursor and update the data.
I tried making the first transaction and object stores as global variables, but that doesn't work either.
It seems kind of ridiculous to only let you perform one transaction per page. How else am I suppose to initially load data and THEN allow the user to update it? Am I missing something?
Upvotes: 1
Views: 2021
Reputation: 18690
Just a few points:
alert
together with indexedDb calls. indexedDB is asynchronous. alert
is not asynchronous, it halts execution while displayed, which can lead to some strange behavior. It is better to use console.log because that does not halt execution.IDBObjectStore.prototype.get
instead of openCursor
For example, here is some pseudocode following some of the above suggestions:
function connect(name, version, upgrade) {
return new Promise((resolve, reject) => {
var req = indexedDB.open(name, version);
req.onupgradeneeded = upgrade;
req.onsuccess = event => resolve(req.result);
req.onerror = event => reject(req.error);
});
}
function onupgradeneeded(event) {
var db = event.target.result;
db.createObjectStore(...);
}
function updateTodo(db, todo) {
return new Promise((resolve, reject) => {
var tx = db.transaction('todos', 'readwrite');
tx.onerror = event => reject(event.target.error);
tx.oncomplete = resolve;
var store = tx.objectStore('todos');
var req = store.get(todo.title);
req.onsuccess = event => {
// When using get instead of openCursor, we just grab the result
var old = event.target.result;
// If we are updating we expect the current db value to exist
// Do not confuse a get request succeeding with it actually matching
// something. onsuccess just means completed without error.
if(!old) {
const error = new Error('Cannot find todo to update with title ' + todo.title);
reject(error);
return;
}
// Change old todo object props
old.category = todo.category;
old.foo = todo.bar;
// Replace old with a newer version of itself
store.put(old);
};
});
}
function onpageloadInitStuff() {
mybutton.onclick = handleClick;
}
async function handleClick(event) {
var db;
try {
db = await connect('tododb', 1, upgradeneededfunc);
var newtodo = todolist.querySelector('todolist[checked]');
await updateTodo(db, newTodo);
} catch(error) {
alert(error); // it is ok to alert here, still better to console.log though
} finally {
if(db) {
db.close();
}
}
}
Upvotes: 2
Reputation: 183
Iskandar and I had the same idea at the same time. In the UpdateToDo function, I opened a completely seperate connection to the IndexedDB instance and made my new transaction/store connect to that instance.. Working with that request worked
function UpdateToDo() {
var udb = window.indexedDB.open("ToDoApp", 2);
alert("1");
udb.onsuccess = function (event) {
alert("2");
db2 = event.target.result;
var trans2 = db2.transaction(["todos"], "readwrite");
var ostore2 = trans2.objectStore("todos");
alert("3");
var blabla = ostore2.openCursor();
blabla.onsuccess = function (event2) {
Coming from a MS Sql Server/ASP.NET background, opening multuiple connections to the same DB feels counter intuitive.
It seems IndexedDB and Cache API's weren't really stable on Safari until last year and service workers weren't even included on Safari at all until last year (2018). While Android has the global market, in North America, if Apple doesn't support it, there isn't much development... it sort of explains the last of documentation and tutorials for these new features. Good luck, everyone
Upvotes: 1