How to create multiple object stores in IndexedDB - javascript

I don't know if I'm right or wrong. But as I know I can't create a version change transaction manually. The only way to invoke this is by changing the version number when opening the indexed DB connection. If this is correct, in example1 and example2 new objectStore will never be created?
Example1
function createObjectStore(name){
var request2 = indexedDB.open("existingDB");
request2.onupgradeneeded = function() {
var db = request2.result;
var store = db.createObjectStore(name);
};
}
Example2
function createObjectStore(name){
var request2 = indexedDB.open("existingDB");
request2.onsuccess = function() {
var db = request2.result;
var store = db.createObjectStore(name);
};
}
Example3 - This should work:
function createObjectStore(name){
var request2 = indexedDB.open("existingDB", 2);
request2.onupgradeneeded = function() {
var db = request2.result;
var store = db.createObjectStore(name);
};
}
If I want to create multiple objectStore's in one database how can I get/fetch database version before opening the database??
So is there a way to automate this process of getting database version number??
Is there any other way to create objectStore other than that using onupgradeneeded event handler.
Please help. Thanks a lot.
Edit:
Here is same problem that I have:
https://groups.google.com/a/chromium.org/forum/#!topic/chromium-html5/0rfvwVdSlAs

You need to open the database to check it's current version and open it again with version + 1 to trigger the upgrade.
Here is the sample code:
function CreateObjectStore(dbName, storeName) {
var request = indexedDB.open(dbName);
request.onsuccess = function (e){
var database = e.target.result;
var version = parseInt(database.version);
database.close();
var secondRequest = indexedDB.open(dbName, version+1);
secondRequest.onupgradeneeded = function (e) {
var database = e.target.result;
var objectStore = database.createObjectStore(storeName, {
keyPath: 'id'
});
};
secondRequest.onsuccess = function (e) {
e.target.result.close();
}
}
}

The only way you can create an object store is in the onupgradeneeded event. You need a version_change transaction to be able to change the schema. And the only way of getting a version_change transaction is through a onupgradeneeded event.
The only way to trigger the onupgradeneeded event is by opening the database in a higher version than the current version of the database. The best way to do this is keeping a constant with the current version of the database you need to work with. Every time you need to change the schema of the database you increase this number. Then in the onupgradeneeded event, you can retrieve the current version of the database. With this, you can decide which upgrade path you need to follow to get to the latest database schema.
I hope this answers your question.

Related

Failed to execute 'put' on 'IDBObjectStore': The transaction has finished

I am trying to update an entry in my simple to do app with indexedDB, however I am getting Failed to execute 'put' on 'IDBObjectStore': The transaction has finished.
I can't seem to figure out why it won't finish the transaction, I tried the debugger and it stops at this line: var updateNameRequest = tasksStore.put( requestForItem.result.name, Number(requestForItem.result.id)) Please see the snippet I included below. For additional context creating, reading, and deleting work just fine it's just updating data that I'm having trouble with
I also tried to implement the openCursor technique which I got from Mozilla which I commented out since it also doesn't work (I get the same behavior) Check out my repo I know it's still very messy :(
const request = window.indexedDB.open("toDoList", 2);
var db;
request.onsuccess = function (event) {
console.log("check out some data about our opened db: ", request.result);
db = event.target.result; // result of opening the indexedDB instance "toDoList"
getTasks(); //just a function to retrieve data
};
$(document).on("click", ".editBtn", function () {
var transaction = db.transaction("tasks", "readwrite");
var tasksStore = transaction.objectStore("tasks");
console.log(tasksStore);
let taskId = $(this).attr("idNo");
var requestForItem = tasksStore.get(Number(taskId));
requestForItem.onsuccess = function () {
// console.log(requestForItem.result)
var oldData = requestForItem.result;
// prepopulate the input
$(".editInput").val(requestForItem.result.name);
$(".saveBtn").click(function () {
requestForItem.result.name = $(".editInput").val().trim()
console.log( requestForItem.result)
var updateNameRequest = tasksStore.put( requestForItem.result.name, Number(requestForItem.result.id))
console.log("-------------", updateNameRequest.transaction) // doesn't get to this line
updateNameRequest.onerror = function() {
console.log("something went wrong")
console.log(updateNameRequest.error)
};
updateNameRequest.onsuccess = function() {
console.log("here")
$(".editInput").val("")
getTasks();
};
});
};
Indexed DB transactions auto-commit when all requests have been completed and no further requests were made before control returns to the event loop. Put another way - you can make new requests in the success or error callback from a previous request, but not in other asynchronous callbacks such as event handlers.
You need to start a new transaction within the click handler, because any previous transaction will have autocommitted.

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.

find and findOne in indexedDB

I understand indexedDB doesn't have findOne or find functions (as MongoDB does), but what I want to accomplish is similar to those functions.
I have a data store in indexedDB. I created an index on the store using, say, the stop_id.
I want to find, in the store, all documents with the stop_id. Multiple objects might have one stop_id value.
What I have:
I am trying to cheat a bit (please correct me if there's a better way)
// this function is called from html via angularjs
$scope.findOne = function(stop_id) {
var db;
var request = indexedDB.open("Trans");
request.onerror = function(event) {
alert("Couldn't connect to Database");
};
request.onsuccess = function(event) {
db = event.target.result;
var objectStore = db.transaction("times").objectStore("times");
var index = objectStore.index("stop_id");
var range = IDBKeyRange.only(stop_id);
// call something here to retrieve
// One or All documents with the ID of stop_id
// passed in from the html
}
}
I would then want to call in html as so:
<div class="medium-6 columns" ng-repeat="stops in objects | orderBy: 'stop_name'">
<div class="card hoverable">
<div class="content">
<span class="title">{{stops.stop_name}}</span><small class="float-right">{{stops.stop_id}}</small>
<!-- this function will search another object store, then retrieve all documents matching the stop_id -->
{{ findOne(stops.stop_id)}}</p>
</div>
</div>
</div>
I am considering the above approach because indexedDB doesn't support joins natively, and I will want to use native indexeddb workarounds to achieve retrieving extra data relating to an id in another datastore on the fly. Performance isn't a concern. Both Data store documents wont be more than 150 items.
Use the IDBIndex get or openCursor method.
// for one
var range = IDBKeyRange.only(myId);
var getRequest = index.get(range);
getRequest.onsuccess = function(event) {
var result = event.target.result;
console.log(result);
};
// for multiple ...
var getRequest = index.openCursor(range);
var documentsFound = [];
getRequest.onsuccess = function(event) {
var request = event.target;
var cursor = request.result;
if(!cursor) {
console.log('no match found, or no more matches found');
someFunction(documentsFound);
} else {
console.log('Found:', cursor.value);
documentsFound.push(cursor.value);
cursor.advance();
}
};
If multiple objects in the store can have the same stop_id, then make sure you do not use a unique:true flag when creating the index in your onupgradeneeded handler.

indexeddb Invalid state error by call-stack?

Again, i got some question on indexeddb. I´m getting a
InvalidStateError: A Mutation operation was attempted on a database
that did not allow mutations.
and also an
AbortError
Here is my code:
DB_LINK.prototype.pushStoreNumeric = function ()
{
// Saving Values
var _temp = 0;
var _version = this.link.version;
var _name = this.link.name;
var that = this;
var _objectStoreNames = this.link.objectStoreNames;
// Close DB
this.link.close();
this.state = 4;
// Reopen Database
this.req = indexedDB.open(_name,_version+1); // Abort error here
this.req.onupgradeneeded = function () {
that.state = 1;
// Get Number of object stores
_temp = _objectStoreNames.length;
if(_temp != 0)
{
// Already object stores: read highest value
_temp = parseInt(_objectStoreNames[_objectStoreNames.length - 1]);
}
that.link.createObjectStore(_temp); // InvalidStateError here
};
I have marked per comment where the errors occur.
The InvalidStateError occures first, the AbortError follows.
I am calling this function inside another onsuccess function of the same database. Might this be the problem?
What is this.link? That's probably the problem. You need to be doing createObjectStore on the database instance created by the indexedDB.open request. So either this.req.result.createObjectStore or (if you change to this.req.onupgradeneeded = function (e) {) you could use e.target.result.createObjectStore.
More generally, I can't really comment on what your code is supposed to be doing because I can only see a snippet, but it looks really weird how you are incrementing the version every time this is called. Probably you don't actually want to be doing that. You might want to read a bit more documentation.

IndexedDB DOM IDBDatabase Exception 11 even after using oncomplete

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.

Categories