IndexedDB DOM IDBDatabase Exception 11 even after using oncomplete - javascript

I am very new to IndexedDB Concepts. I am trying to Store a list of movies in the IndexedDB and retrieve it. But for some reason when i try to retrieve it there is a DOM IDBDatabase Exception 11 in chrome browser. I try to retrieve it by using a simple alert. I also tried to retrieve the data by putting the alert inside an onComplete event, but this too seems to be a failure. Could someone please let me know what wrong i am doing. Below is my code.
const dbName = "movies";
var request = indexedDB.open(dbName, 1);
request.onerror = function(event) {
alert("Seems like there is a kryptonite nearby.... Please Check back later");
};
request.onsuccess = function(event) {
var db = event.target.result;
var transaction = db.transaction(["movies"],"readwrite");
var objectStore = transaction.objectStore("movies");
var request1 = objectStore.get("1");
request1.result.oncomplete=function(){
alert("The movie is"+request1.result.name);//This is the place where i get the error
}
};
request.onupgradeneeded = function(event) {
db = event.target.result;
var objectStore = db.createObjectStore("movies", { keyPath: "movieid" });
objectStore.createIndex("name", "name", { unique: false });
objectStore.createIndex("runtime", "runtime", { unique: false });
for (var i in movieDataToStore) {
objectStore.add(movieDataToStore[i]);
}};

I still do not know what was wrong with the last program. i re-wrote the above program and it worked like a charm. here is the code. Hope this helps anyone who is stuck with this problem. Also if anyone figures out what went wrong the last time please share your thoughts.
var db; //database will be stored in this value when success is called
var movieDataToStore = [{ movieid: "1", name: "Keep my Storage Local", runtime:"60"},
{ movieid: "2", name: "Rich Internet Conversations", runtime:"45"},
{ movieid: "3", name: "Applications of the Rich and Famous", runtime:"30"},
{ movieid: "4", name: "All Jump All eXtreme", runtime:"45"}];
window.query = function() {
db.transaction("movies").objectStore("movies").get("1").onsuccess = function(event) {
alert("QUERY: CThe first movie is" + event.target.result.name);
};};
window.onload = function() {
if (!window.indexedDB) {
window.alert("Your browser doesn't support a stable version of IndexedDB. Such and such feature will not be available.")
}
else{
var request = indexedDB.open("movies", 1);
request.onerror = function(event) {
alert("Seems like there is a kryptonite nearby.... Please Check back later");
};
request.onsuccess = function(event) {
db = this.result;
query();
};
request.onupgradeneeded = function(event) {
var db = event.target.result;
if(db.objectStoreNames.contains("movies")) {
db.deleteObjectStore("movies");
}
var objectStore = db.createObjectStore("movies", { keyPath: "movieid"});
objectStore.createIndex("name", "name", { unique: false });
objectStore.createIndex("runtime", "runtime", { unique: false });
for (var i in movieDataToStore) {
objectStore.add(movieDataToStore[i]);
}
};
}
};

I think it is bad practice to insert data in the onupgradeneeded context. You should be doing this separately in an unrelated function at some other time. In fact, attempting to insert the data on a database whose version was incremented since last page load will automatically trigger the upgradeneeded event for you.
While many of the online examples shove the database connection handle (your db var) into some global scope variable, this is also a bad practice that will lead to errors down the road. Only access the db var within your callbacks as a parameter. In other words, your openRequest.onsuccess function should pass the db variable to the query function. This also reduces the chances of any garbage collection issues later and leaving database connections open (which the designers of indexedDB allow for, but should generally be avoided).
If your movie ids are integers, it isn't clear to me why you are storing and retrieving them as strings. You can store integer values. You can pass an integer to store.get.
You are using for...in inappropriately. It will work, but for...in is intended for looping over the keys of object literals (like var x = {key:value}). Use a normal for loop or use array.foreach when iterating over your movies array.
As you found out in your fixed code, it is better to use request.onsuccess and request.onerror. There is also a transaction.oncomplete. But I am not sure there is a request.oncomplete. What happens is you are setting the oncomplete property of an IDBRequestObject but this does nothing since the code never triggers it.
DOM 11 usually signals you tried to access a table that does not exist or is in an incorrect state. Usually this happens due to mistakes elsewhere, like onupgradeneeded never getting called when connecting. It is confusing, but given the way your code is setup, basically the db gets created the first time your page loads, but then never gets created again, so while developing, if you made changes, but do not increment your db version, you will never see them.

Related

Getting object store already exists inside onupgradeneeded

My code is as follows (usually naming convention for the well-known objects):
var DBOpenRequest = window.indexedDB.open("messages", 6);
//...
DBOpenRequest.onupgradeneeded = function(event) {
console.log("Need to upgrade.");
var db = event.target.result;
console.log(db);
db.onerror = function(event) {
console.log("Error upgrading.");
};
// Create an objectStore for this database
var objectStore = db.createObjectStore("messages", { keyPath: "id", autoIncrement: true });
};
This ran fine for versions 3 and 4. When it came to version 5, I get the error:
Failed to execute 'createObjectStore' on 'IDBDatabase': An object store with the specified name already exists.
at IDBOpenDBRequest.DBOpenRequest.onupgradeneeded
Isn't the createObjectStore operating on a new version of the database which is empty? How do I fix the error?
I happened to log the db object and the details are below:
I am curious why the version number is different in the summary line and when expanded.
Isn't the createObjectStore operating on a new version of the database which is empty?
When you get upgradeneeded the database is in whatever state you left it in before. Since you don't know what versions of your code a user will have visited, you need to look at the event's oldVersion to find out what that was. The typical pattern is something like this:
var rq = indexedDB.open('db', 5);
rq.onupgradeneeded = function(e) {
var db = rq.result;
if (e.oldVersion < 1) {
// do initial schema creation
db.createObjectStore('users');
}
if (e.oldVersion < 2) {
// do 1->2 upgrade
var s = db.createObjectStore('better_users');
s.createIndex('some_index', ...);
db.deleteObjectStore('users'); // migrating data would be better
}
if (e.oldVersion < 3) {
// do 2->3 upgrade
rq.transaction.objectStore('better_users').createIndex('index2', ...);
}
if (e.oldVersion < 4) {
// do 3->4 upgrade
db.createObjectStore('messages', ...);
}
if (e.oldVersion < 5) {
// do 4->5 upgrade
// ...
}
}
I am curious why the version number is different in the summary line and when expanded.
That one is subtle... I believe at the point where the 5 was logged the database had started the upgrade. But because an exception was thrown in the upgradeneeded handler the upgrade was aborted, and the version number was rolled back to 4 before the details were logged.
The best way to upgrade the DB is checking if the store name is already there. In this example I'm using https://npmjs.com/idb
openDB('db-name', version, {
upgrade(db, oldVersion, newVersion, transaction) {
if(!db.objectStoreNames.contains('messages')) {
db.createObjectStore('messages', { keyPath: "id", autoIncrement: true })
}
}
})
If you need to check if an indexName already exist, you can get the objectStore and check for the indexNames property if it contains the indexName you need.
openDB('db-name', version, {
upgrade(db, oldVersion, newVersion, transaction) {
const storeName = transaction.objectStore('storeName')
if(!storeName.indexNames.contains('indexName')) {
storeName.createIndex('indexName', 'propertyName', { unique: false });
}
}
})
Using indexDB API with indexNames and objectStoreNames to check if something is either there or not makes my code way more reliable and easy to maintain, it is also briefly mentioned on Working with IndexDB Using database versioning

How to dynamically create and populate multiple objectStores on HTML5 IndexedDB in the same transaction?

Working on the IndexedDB API, I'm creating many objectStores that belong to the same database, in one transaction, when the user loads a webpage.
I order to do so, I created an object which contains many objectStores to be created, each one has it's name, data and index.
Then a function runs the object and effectively creates Database, objectStores and indexes for each one.
However of all OS's created, just the last member of the object gets populated. Say of 5 objects to be created and populated, 5 are created but only the last one is populated.
Clearly is a problem of overwriting or some issue related to the JS stack or asynchronicity.
I appreciate any help to make the code populate all OS not the last one.
My browser is Chrome 56, I fetch data from an API whose response is OK, and I'm coding on vanillajs. I appreciate your help in vanillajs, there is no way to use any library or framework different from what the modern Web Platform offers.
Here is the code:
On the HTML side, this is an example of the object:
var datastores = [{osName:'items', osEndpoint: '/api/data/os/1/1', osIndex:'value'}, {osName:'categories', osEndpoint: '/api/data/os/2/1', osIndex: 'idc'}];
On javascript:
var request = indexedDB.open(DB_NAME, DB_VERSION); // open database.
request.onerror = function (e) { // error callback
console.error("error: " + e.target.errorCode);
};
request.onupgradeneeded = function (e) { // the onupgradeneeded event which creates all schema, dataabase, objectstores and populates OS.
var db = this.result;
for (var i in datastores) { // loop the objectStore object.
var objectStore = db.createObjectStore(datastores[i].osName, {keyPath: "id"});
TB_NAME = datastores[i].osName; // instantiate each objectStore name.
objectStore.createIndex(datastores[i].osIndex, datastores[i].osIndex, { unique: false }); // create each index.
objectStore.transaction.oncomplete = function(e) { // oncomplete event, after creating OS...
fetchGet(datastores[i].osEndpoint, popTable); // runs a function to fetch from a designated endpoint and calls a function.
};
}
}
Now the functions: to fetch data and to populate data:
function fetchGet(url, function) { // fetch from API.
fetch(url, {
method: 'GET'
}).then(function(response) {
return response.json();
}).then(function(json) {
popTable (json);
}).catch(function(err) {
console.log('error!', err);
});
}
function popTable(json) {
var m = 0;
var tx = db.transaction(TB_NAME, "readwrite");
tx.oncomplete = function(e) {
console.log("Completed Transaction " + TB_NAME);
};
tx.onerror = function(e) {
console.error("error: " + e.target.errorCode);
};
var txObjectStore = tx.objectStore(TB_NAME);
for (m in json) {
var request = txObjectStore.add(json[m]);
request.onsuccess = function (e) {
console.log('adding... ' );
};
}
}
The for (var i in datastores) loop runs synchronously, updating the global TB_NAME variable every time. When the loop finishes, TB_NAME will be holding the name of the last object store.
By the time the asynchronous popTable calls run, TB_NAME will forever be holding the name of the last store, so that's the only one that will update. Try adding logging to popTable to see this.
You'll need to pass the current value of the store name along somehow (e.g. as an argument to fetchGet). Also note that although you pass popTable as a parameter when calling fetchGet you're not actually accepting it as an argument.
...
Specific changes:
Change how you call fetchGet to include the store name:
fetchGet(datastores[i].osEndpoint, popTable, datastores[i].osName);
Change the fetchGet function to accept the args:
function fetchGet(url, func, name) {
And then instead of calling popTable directly, do:
func(json, name);
And then change the definition of popTable to be:
function popTable(json, name) {
... and use name in the transaction.

Counting the number of records in an object store in indexedDB

I want to basic count the number of records in my indexedDB database.
Currently my code looks like
Javascript
var transaction = db.transaction(["data"], "readonly");
var objectStore = transaction.objectStore("data");
var cursor = objectStore.openCursor();
var count = objectStore.count();
console.log(count);
I would love for this to say output just 3, but instead i get.
Output
IDBRequest {onerror: null, onsuccess: null, readyState: "pending", transaction: IDBTransaction, source: IDBObjectStore…}
error: null
onerror: null
onsuccess: null
readyState: "done"
result: 3
source: IDBObjectStore
transaction: IDBTransaction
__proto__: IDBRequest
Which is correct but I just want it to say 3 not loads of other stuff.
Bring back record count with a little less code:
var store = db.transaction(['trans']).objectStore('trans');
var count = store.count();
count.onsuccess = function() {
console.log(count.result);
}
Try something like this:
var transaction = db.transaction(["data"], "readonly");
var objectStore = transaction.objectStore("data");
var count = objectStore.count();
count.onsuccess = function() {
console.log(count.result);
};
A little bit of introduction in order. From my personal docs on transactions:
Certain transactions return data, or "results", from the database.
These transactions are called "requests" and with the exception of
database opening, the values are always various combinations of object
"keys" and "values" and instances of IDBRequest. Request transactions
are just that: a transaction request," namely the act of asking for
something rather than the getting of it. A programmer encounters them
when dealing with IDBObjectStore, IDBIndex or IDBCursor objects.
What you're looking at is an IDBRequest object, which is returned by the count() method. That represents the request for data, and not the data itself.
The data itself is available after the complete event fires, and can be accessed via the IDBRequest.result property.
Here's a tested count method from my library, dash:
API.entries.count = function (count_ctx) {
var request;
if (API.exists(count_ctx.index)) {
count_ctx.idx = count_ctx.objectstore.index(count_ctx.index);
request = API.isEmpty(count_ctx.key) ? count_ctx.idx.count() : count_ctx.idx.count(count_ctx.key);
} else {
request = API.isEmpty(count_ctx.key) ? count_ctx.objectstore.count() : count_ctx.objectstore.count(count_ctx.key);
}
count_ctx.transaction.addEventListener('error', function (event) {
count_ctx.error = event.target.error.message;
API.error(count_ctx);
});
request.addEventListener('success', function () {
count_ctx.total = request.result;
API.success(count_ctx);
});
I'll note that I probably should have used the complete event rather than the success event. I can't explain why but sometimes result values are not available in success callbacks.

Safari - "TransactionInactiveError" using Facebook IndexedDB Polyfill

We are using the Facebook IndexedDB Polyfill to allow the IndexedDB API to be utilised in Safari/ Mobile Safari. However, we are experiencing a "TransactionInactiveError" when attempting to update records - the error originates from line 1567 of the Polyfill.js file: if (!me._active) throw new util.error("TransactionInactiveError");
Here's a quick example I've put together. Simply add the Facebook Polyfill script tag reference and run in Safari:
var db;
var request = indexedDB.open("polyfillTest");
request.onupgradeneeded = function () {
// The database did not previously exist, so create object stores and indexes.
db = request.result;
var store = db.createObjectStore("books", {keyPath: "isbn"});
var titleIndex = store.createIndex("by_title", "title", {unique: true});
var authorIndex = store.createIndex("by_author", "author");
// Populate with initial data.
store.put({title: "Quarry Memories", author: "Fred", isbn: 123456});
store.put({title: "Water Buffaloes", author: "Fred", isbn: 234567});
store.put({title: "Bedrock Nights", author: "Barney", isbn: 345678});
updateItem(store);
};
request.onsuccess = function () {
db = request.result;
};
function updateItem(store) {
var request = store.get(123456);
request.onsuccess = function () {
var book = request.result;
book.title = "New Title";
book.author = "New Author";
var updateRequest = store.put(book);
updateRequest.onsuccess = function (evt) {
console.log("Book updated successfully.");
};
updateRequest.onerror = function (evt) {
console.error("Book could not be updated.");
};
};
}
Any help appreciated!
Many thanks
Transactions are typically kept active until the last callback with a reference releases that reference. So this suggests to me your transaction is auto-commiting.
I suspect it may have something to do with your re-use of the versionchange transaction for puts and gets. After many headaches with this issue, in my library I've opted for a model where I allow all version change transactions to fully commit before trying to do CRUD operations on the same store.
I'm not fully able to explain why, but based on many days of frustration, keeping long-lived versionchanges seems to be a bad idea.

Do I need to refresh a page to see if the Indexed DB was reset?

I started working with Indexed DB for HTML 5 but I am obtaining some strange results. The first one is that I try to clear my database but I only see that it was reset if I refresh the site. Is that how it should be?
I have seen other sample codes which it does not happen in this way.
The onsuccess is called but the DB shown, by the update method, is the same that was before...
Here is my reset function:
function resetDB()
{
try
{
if (localDatabase != null && localDatabase.db != null)
{
var store = localDatabase.db.transaction("patients", "readwrite").objectStore("patients");
store.clear().onsuccess = function(event)
{
alert("Patients DB cleared");
update_patients_stored();
};
}
}
catch(e)
{
alert(e);
}
}
onsuccess can fire before the results are actually updated in the database (see this answer to a question I asked here. So if update_patients_stored is reading from the database, it might still see the old data. If you use a transaction's oncomplete, then you won't have that problem.
If that is indeed causing your issue, then this will fix it:
var tx = localDatabase.db.transaction("patients", "readwrite");
tx.objectStore("patients").clear();
tx.oncomplete = function(event)
{
alert("Patients DB cleared");
update_patients_stored();
};

Categories