I am working on storing some data in the indexedDb.
I have created a method which saves the data into the indexedDb. I have stored exactly 49 records. I am trying to retrieve all of them. I have written the below code for getting the values. No other code except this line exist in my js file.
function crap() {
var indexedDb = window.indexedDB || window.webkitIndexedDB || window.msIndexedDB;
var IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange;
var openedDb = indexedDb && indexedDb.open;
var isIndexDbTransactionPossible = window.IDBTransaction || window.webkitIDBTransaction;
if (isIndexDbTransactionPossible) {
isIndexDbTransactionPossible.READ_WRITE = isIndexDbTransactionPossible.READ_WRITE || 'readwrite';
isIndexDbTransactionPossible.READ_ONLY = isIndexDbTransactionPossible.READ_ONLY || 'readonly';
}
var request = indexedDb.open('Offline', DB_VERSION);
request.onupgradeneeded = function(e) {
var db = e.target.result;
if (db.objectStoreNames.contains('tab')) {
db.deleteObjectStore('tab');
}
var store = db.createObjectStore('tab', {keyPath: 'id', autoIncrement: true});
};
request.onsuccess = function(e) {
console.log("DB opened");
var db = e.target.result;
var store= db.transaction('tab', IDBTransaction.READ_ONLY).objectStore('tab');
var cursor = store.openCursor();
cursor.onsuccess = function(event) {
var c = event.target.result;
if (c) {
console.log("New value")
c.continue();
}
};
};
}
I am seeing "New Value" printed 124 times. I am not sure why the cursor.continue() is not returning null after 49th attempt. Any help is much appreciated.
I am positive that this method is not called more than one time. "DB opened" is logged only one.
Just use the getAll function:
var allRecords = store.getAll();
allRecords.onsuccess = function() {
console.log(allRecords.result);
};
Read more in the documentation: Working with IndexedDB
Instead of checking readyState, just check for whether the cursor is defined in your cursor request callback. Here is an example. I modified the names of your variables slightly for clarity.
cursorRequest.onsuccess = function(event) {
var cursor = event.target.result;
if(cursor) {
var value = cursor.value;
console.log('New value:', value);
cursor.continue();
} else {
// Undefined cursor. This means either no objects found,
// or no next object found
// Do not call cursor.continue(); in this else branch because
// there are no more objects over which to iterate.
// Coincidentally, this also means we are done iterating.
console.log('Finished iterating');
}
}
Related
I want to execute this query
select * from properties where propertyCode IN ("field1", "field2", "field3")
How can I achieve this in IndexedDB
I tried this thing
getData : function (indexName, params, objectStoreName) {
var defer = $q.defer(),
db, transaction, index, cursorRequest, request, objectStore, resultSet, dataList = [];
request = indexedDB.open('test');
request.onsuccess = function (event) {
db = request.result;
transaction = db.transaction(objectStoreName);
objectStore = transaction.objectStore(objectStoreName);
index = objectStore.index(indexName);
cursorRequest = index.openCursor(IDBKeyRange.only(params));
cursorRequest.onsuccess = function () {
resultSet = cursorRequest.result;
if(resultSet){
dataList.push(resultSet.value);
resultSet.continue();
}
else{
console.log(dataList);
defer.resolve(dataList);
}
};
cursorRequest.onerror = function (event) {
console.log('Error while opening cursor');
}
}
request.onerror = function (event) {
console.log('Not able to get access to DB in executeQuery');
}
return defer.promise;
But didn't worked. I tried google but couldn't find exact answer.
If you consider that IN is essentially equivalent to field1 == propertyCode OR field2 == propertyCode, then you could say that IN is just another way of using OR.
IndexedDB cannot do OR (unions) from a single request.
Generally, your only recourse is to do separate requests, then merge them in memory. Generally, this will not have great performance. If you are dealing with a lot of objects, you might want to consider giving up altogether on this approach and thinking of how to avoid such an approach.
Another approach is to iterate over all objects in memory, and then filter those that don't meet your conditions. Again, terrible performance.
Here is a gimmicky hack that might give you decent performance, but it requires some extra work and a tiny bit of storage overhead:
Store an extra field in your objects. For example, plan to use a property named hasPropertyCodeX.
Whenever any of the 3 properties are true (has the right code), set the field (as in, just make it a property of the object, its value is irrelevant).
When none of the 3 properties are true, delete the property from the object.
Whenever the object is modified, update the derived property (set or unset it as appropriate).
Create an index on this derived property in indexedDB.
Open a cursor over the index. Only objects with a property present will appear in the cursor results.
Example for 3rd approach
var request = indexedDB.open(...);
request.onupgradeneeded = upgrade;
function upgrade(event) {
var db = event.target.result;
var store = db.createObjectStore('store', ...);
// Create another index for the special property
var index = store.createIndex('hasPropCodeX', 'hasPropCodeX');
}
function putThing(db, thing) {
// Before storing the thing, secretly update the hasPropCodeX value
// which is derived from the thing's other properties
if(thing.field1 === 'propCode' || thing.field2 === 'propCode' ||
thing.field3 === 'propCode') {
thing.hasPropCodeX = 1;
} else {
delete thing.hasPropCodeX;
}
var tx = db.transaction('store', 'readwrite');
var store = tx.objectStore('store');
store.put(thing);
}
function getThingsWherePropCodeXInAnyof3Fields(db, callback) {
var things = [];
var tx = db.transaction('store');
var store = tx.objectStore('store');
var index = store.index('hasPropCodeX');
var request = index.openCursor();
request.onsuccess = function(event) {
var cursor = event.target.result;
if(cursor) {
var thing = cursor.value;
things.push(thing);
cursor.continue();
} else {
callback(things);
}
};
request.onerror = function(event) {
console.error(event.target.error);
callback(things);
};
}
// Now that you have an api, here is some example calling code
// Not bothering to promisify it
function getData() {
var request = indexedDB.open(...);
request.onsuccess = function(event) {
var db = event.target.result;
getThingsWherePropCodeXInAnyof3Fields(db, function(things) {
console.log('Got %s things', things.length);
for(let thing of things) {
console.log('Thing', thing);
}
});
};
}
I'm trying to use indexedDB.
Some parts of my code works.
In the following example, the first function adds server in my DB, however in Chrome debug console there is an undefined message not related to any line. The server is already added though.
The second function puts records in an array, there is also an undefined message not related to any line.
If I do a console.log(servers); just before return servers; I can see the array content, however if I call the function somewhere else in my code, the returned object is undefined.
var dbName = 'myDBname',
dbServersStoreName = 'servers',
dbVersion = 1,
openDBforCreation = indexedDB.open(dbName, dbVersion);
openDBforCreation.onupgradeneeded = function(e) {
var db = e.target.result;
var objStore = db.createObjectStore(dbServersStoreName, { keyPath: "alias"
});
var index = objStore.createIndex("serversAlias", ["alias"]);
};
function addServerInDB(serverAlias,serverAddress,user,pwd){
var myDB = indexedDB.open(dbName, dbVersion);
myDB.onerror = function() {
var notification = document.querySelector('.mdl-js-snackbar');
notification.MaterialSnackbar.showSnackbar(
{message: 'Error while trying to access internal database'});
}
myDB.onsuccess = function(e) {
var db = e.target.result,
request = db.transaction([dbServersStoreName],
"readwrite").objectStore("servers")
.put({alias:''+serverAlias+'',
address:''+serverAddress+'', login:''+user+'',
passwd:''+pwd+''});
request.onsuccess = function(){
var notification = document.querySelector('.mdl-js-snackbar');
notification.MaterialSnackbar.showSnackbar(
{message: 'Server added'});
}
}
};
function listServersInDB(){
var myDB= indexedDB.open(dbName, dbVersion);
myDB.onerror = function() {
var notification = document.querySelector('.mdl-js-snackbar');
notification.MaterialSnackbar.showSnackbar(
{message: 'Error while trying to access internal database'});
}
myDB.onsuccess = function(e) {
var servers = new Array(),
db = e.target.result,
request = db.transaction(["servers"], "readwrite")
.objectStore("servers")
.openCursor();
request.onsuccess = function(e){
var cursor = e.target.result;
if(cursor){
servers.push(cursor.value);
cursor.continue();
}
return servers;
}
}
};
I do not understand where this undefined comes from and if that is why the listServersInDB() function doesn't work.
You need to learn more about how to write asynchronous Javascript. There are too many errors in your code to even begin reasoning about the problem.
Briefly, don't do this:
function open() {
var openDatabaseRequest = ...;
}
openDatabaseRequest.foo = ...;
Instead, do this:
function open() {
var openDatabaseRequest = ...;
openDatabaseRequest.foo = ...;
}
Next, you don't need to try and open the same database multiple times. Why are you calling indexedDB.open twice? You can open a database to both install it and to start using it immediately. All using the same connection.
Next, I'd advise you don't name the database open request as 'myDB'. This is misleading. This is an IDBRequest object, and more specifically, an IDBOpenRequest object. A request isn't a database.
Next, you cannot return the servers array from the request.onsuccess at the end. For one this returns to nowhere and might be source of undefined. Two this returns every single time the cursor is advanced, so it makes no sense at all to return return servers multiple times. Three is that this returns too early, because it cannot return until all servers enumerated. To properly return you need to wait until all servers listed. This means using an asynchronous code pattern. For example, here is how you would do it with a callback:
function listServers(db, callbackFunction) {
var servers = [];
var tx = db.transaction(...);
var store = tx.objectStore(...);
var request = store.openCursor();
request.onsuccess = function() {
var cursor = request.result;
if(cursor) {
servers.push(cursor.value);
cursor.continue();
}
};
tx.oncomplete = function() {
callbackFunction(servers);
};
return 'Requested servers to be loaded ... eventually callback will happen';
}
function connectAndList() {
var request = indexedDB.open(...);
request.onsuccess = function() {
var db = request.result;
listServers(db, onServersListed);
};
}
function onServersListed(servers) {
console.log('Loaded servers array from db:', servers);
}
When you call a function that does not return a value, it returns undefined. All functions in JavaScript return undefined unless you explicitly return something else.
When you call a function from the devtools console, and that function returns undefined, then the console prints out '-> undefined'. This is an ordinary aspect of using the console.
If you want to get a function that returns the list of servers as an array, well, you cannot. The only way to do that in a pretend sort of way, is to use an 'async' function, together with promises.
async function getServers() {
var db = await new Promise(resolve => {
var request = indexedDB.open(...);
request.onsuccess = () => resolve(request.result);
});
var servers = await new Promise(resolve => {
var tx = db.transaction(...);
var request = tx.objectStore(...).getAll();
request.onsuccess = () => resolve(request.result);
});
return servers;
}
One more edit, if you want to call this from the console, use await getServers();. If you do not use the top-level await in console, then you will get the typical return value of async function which is a Promise object. To turn a promise into its return value you must await it.
Clear and helpfull explanations, Thank you.
I open database multiple times beacause the first time is for checking if DB needs an upgrade and doing something if needed. I'll add 'db.close()' in each functions.
Then, I tried your exemple and the result is the same:
console.log('Loaded servers array from db:', servers); works
but return servers; Don't work.
And in console there is already an undefined without related line :
Screenshot
I want to have persistent or persistent-ish storage in a Chrome extension - so what I'm currently trying to do is create an Indexeddb database, and query that from another page.
Here's something from one of my content scripts:
chrome.extension.sendRequest({
"type": "search",
"text": char
},
Which calls this:
search: function(text) {
var entry = this.db_query(text);
if (entry != null) {
for (var i = 0; i < entry.data.length; i++) {
var word = entry.data[i][1];
}
}
return entry;
}
Which goes here:
db_query: function(text) {
var background = chrome.extension.getBackgroundPage();
console.log(background);
var open = indexedDB.open("CEDICT", 1);
var db = open.result;
var tx = db.transaction("CEDICT", "readwrite");
var store = tx.objectStore("CEDICT");
var index2 = store.index("simplified");
var getData = index2.get(text);
getData.onsuccess = function() {
console.log(getData.result);
return getData.result;
};
tx.oncomplete = function() {
db.close();
};
},
Which however is providing an empty indexedDB, despite this being in a background script:
var indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB || window.shimIndexedDB;
var open = indexedDB.open("CEDICT", 1);
open.onupgradeneeded = function() {
var db = open.result;
var store = db.createObjectStore("CEDICT", {autoIncrement:true});
var index1 = store.createIndex("traditional", 'traditional');
var index2 = store.createIndex("simplified", 'simplified');
};
open.onsuccess = function() {
var db = open.result;
var tx = db.transaction("CEDICT", "readwrite");
var store = tx.objectStore("CEDICT");
store.put({"traditional": "三體綜合症", "simplified": "三体综合症", "tones": ["1", "3", "1", "2", "4"]});
store.put({"traditional": "□", "simplified": "□", "tones": ["1"]});
store.put({"traditional": "○", "simplified": "○", "tones": ["2"]});
etc...
tx.oncomplete = function() {
db.close();
};
Why is indexedDB empty? Am I doing something wrong? The first time I reloaded the plugin after adding this, it worked correctly, giving me values I wanted, but since then it hasn't worked and it has returned empty values.
Here's an error I get:
_generated_background_page.html:1 Error in event handler for extension.onRequest: InvalidStateError: Failed to read the 'result' property from 'IDBRequest': The request has not finished.
Does this just mean I need a promise or something?
You cannot return the value from return getData.result; as the code suggests. You will need to become more familiar with async js to use indexedDB.
Just to give a brief example, pretend you had a button, and an event listener on this button. Could you return a value from the event listener function? No. Similarly, you cannot return the value here.
Briefly, you can pass the result to a callback function instead. Or use a promise.
function db_query(..., callbackFunction) {
getData.onsuccess = function(){
// Instead of returning, call the callback function
callbackFunction(getData.result);
};
}
Then, instead of getting the result of db_query, you instead continue execution within the callbackFunction body.
db_query(..., function myCallback(value) {
console.log(value);
});
I have an OK understanding of JS but am ultimately still learning. I'm trying to recreate a PHP/mySQL project over to IndexedDB and can't work out why I'm seeing an error here.
The IndexedDB connection works as expected. The first function (createItem) functions fine, however the second function (getItems) is returning an error claiming that the "db" variable is undefined. It's a global variable so should be accessible by the function's scope, and the createItem function has no problem using it. Can anyone help me see what I've missed here.
// GLOBAL DB VAR
var db;
// WAIT FOR DOM
document.addEventListener("DOMContentLoaded", function(){
// IF INDEXED DB CAPABLE
if("indexedDB" in window) {
// OPEN DB
var openRequest = indexedDB.open("newTabTest",1);
// CREATE NEW / UPDATE
openRequest.onupgradeneeded = function(e) {
// Notify user here: creating database (first time use)
var thisDB = e.target.result;
// Create "items" table if it doesn't already exist
if(!thisDB.objectStoreNames.contains("items")) {
var store = thisDB.createObjectStore("items", {keyPath: "id", autoIncrement: true});
store.createIndex("name","name", {unique:true});
store.createIndex("folder","folder", {unique:false});
store.createIndex("dial","dial", {unique:false});
}
}
openRequest.onsuccess = function(e) {
// Success- set db to target result.
db = e.target.result;
}
openRequest.onerror = function(e) {
// DB ERROR :-(
}
}
},false);
// CREATE ITEM FUNCTION
function createItem(n,u,f,c) {
var transaction = db.transaction(["items"],"readwrite");
var store = transaction.objectStore("items");
var item = {
name: n,
url: u,
folder: f,
colour: c,
dial: 0,
order: 100
}
var request = store.add(item);
request.onerror = function(e) {
console.log("An error occured.");
}
request.onsuccess = function(e) {
console.log("Successfully added.")
}
};
// GET ITEM(S) FUNCTION
// Specify index and key value OR omit for all
function getItems(callback, ind, key) {
var transaction = db.transaction(["items"],"readonly");
var store = transaction.objectStore("items");
var response = [];
// If args are omitted - grab all items
if(!ind | !key) {
var cursor = store.openCursor();
cursor.onsuccess = function(e) {
var res = e.target.result;
if(res) {
var r = {
"name": res.value['name'],
"url": res.value['url'],
"folder": res.value['folder'],
"colour": res.value['colour'],
"dial": res.value['dial'],
"order": res.value['order']
};
response.push(r);
res.continue();
}
}
cursor.oncomplete = function() {
callback(response);
}
} else {
// If both args are specified query specified index
store = store.index(ind);
var range = IDBKeyRange.bound(key, key);
store.openCursor(range).onsuccess = function(e) {
var res = e.target.result;
if(res) {
var r = {
"name": res.value['name'],
"url": res.value['url'],
"folder": res.value['folder'],
"colour": res.value['colour'],
"dial": res.value['dial'],
"order": res.value['order']
};
response.push(r);
res.continue();
}
}
cursor.oncomplete = function() {
callback(response);
}
}
};
As you've figured out in comments, you do have to stick the db-dependent actions inside a function called from a success handler.
This callback-based programming quickly becomes a pain, and a common solution for that is to use promises.
However, making IndexedDB work with promises is still work-in-progress (see https://github.com/inexorabletash/indexeddb-promises if you're interested).
If your goal is to get something done (and not to learn the bare IndexedDB APIs), perhaps you'd be better off finding a wrapper library for IndexedDB (can't recommend one though, since I've not tried working seriously with IDB yet).
I would not recommend using a variable like 'db' as you have in your example. If you are new to reading and writing asynchronous Javascript, you are just going to cause yourself a lot of pain. There are better ways to do it. It takes several pages to explain and is explained in many other questions on StackOverflow, so instead, very briefly, consider rewriting your code to do something like the following:
function createItem(db, ...) {
var tx = db.transaction(...);
// ...
}
function openIndexedDBThenCreateItem(...) {
var openRequest = indexedDB.open(...);
openRequest.onsuccess = function(event) {
var db = event.target.result;
createItem(db, ...);
};
}
function getItems(db, callback, ...) {
var tx = db.transaction(...);
var items = [];
tx.oncomplete = function(event) {
callback(items);
};
// ...
var request = store.openCursor(...);
request.onsuccess = function(event) {
var request = event.target;
var cursor = event.target.result;
if(cursor) {
var item = cursor.value;
items.push(item);
cursor.continue();
}
};
}
function openIndexedDBThenGetItems(db, callback, ...) {
var openRequest = indexedDB.open(...);
openRequest.onsuccess = function(event) {
var db = event.target.result;
getItems(db, callback, ...);
};
}
Also, you don't need to wait for DOMContentLoaded to start using indexedDB. It is immediately available.
If you get the above code, then you can consider a further improvement of adding a simple helper function:
function openIndexedDB(callback) {
var openRequest = indexedDB.open(...);
openRequest.onerror = callback;
openRequest.onsuccess = callback;
}
And then rewrite the examples like this:
function openIndexedDBThenCreateItem(...) {
openIndexedDB(function onOpen(event) {
if(event.type !== 'success') {
console.error(event);
} else {
var db = event.target.result;
createItem(db, ...);
}
});
}
function openIndexedDBThenGetItems(...) {
openIndexedDB(function onOpen(event) {
if(event.type !== 'success') {
console.error(event);
} else {
var db = event.target.result;
getItems(db, ...);
}
});
}
I am trying to use an index.openCursor(keyRange.only or keyRange.bound Provided here) to access one or more records using an index on a table created with autoIncrement: true. I have tried multiple variations with no success. Can someone show me a working example using the following code as a template:
window.indexedDB = window.indexedDB || window.webkitIndexedDB
|| window.mozIndexedDB || window.msIndexedDB;
var ixDb;
var ixDbIndexTest = function () {
//Open or create the requested IndexedDB Database
var ixDbRequest = window.indexedDB.open("testDBindexes", 2);
ixDbRequest.onupgradeneeded = function (e) {
ixDb = ixDbRequest.result || e.currentTarget.result;
objectStore =
ixDb.createObjectStore("demoOS",
{ keyPath: "id", autoIncrement: true });
objectStore.createIndex("ixdemo", "Field1",
{ unique: false, multiEntry: false });
//define new dummy record
var newRecord = {};
newRecord.Field1 = "222";
newRecord.Field2 = "333";
newRecord.Field3 = "444";
var request = objectStore.add(newRecord);
request.onsuccess = function (e) {
var index = objectStore.index('ixdemo');
var range = IDBKeyRange.only("222");
var cursorRequest = index.openCursor(range);
cursorRequest.onsuccess = function(e) {
var cursor = cursorRequest.result || e.result;
alert(cursor.value);
cursor.continue();
}
}
};
};
window.onload = ixDbIndexTest;
Update:
I modified the demo script to work in both Firefox and older Chrome versions that still use setVersion. However, you would need to add additional version checking logic for Chrome since the current logic runs setVersion every time the script runs.
window.indexedDB = window.indexedDB || window.webkitIndexedDB
|| window.mozIndexedDB || window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction
|| window.mozIDBTransaction || window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange ||
window.mozIDBKeyRange || window.msIDBKeyRange;
var ixDb;
var ixDbIndexTest = function () {
//Open or create the requested IndexedDB Database
var ixDbRequest = window.indexedDB.open("testDBindexes", 1);
ixDbRequest.onsuccess = function(e) {
ixDb = ixDbRequest.result || e.currentTarget.result;
if (typeof ixDb.setVersion === "function") {
ixDbVersionRequest = ixDb.setVersion(1);
ixDbVersionRequest.onsuccess = function (e) {
indexTest();
};
}
else {
ixDbRequest.onupgradeneeded = function (e) {
indexTest();
};
}
}
};
window.onload = ixDbIndexTest;
function indexTest() {
var objectStore = ixDb.createObjectStore("demoOS",
{ keyPath: "id", autoIncrement: true });
objectStore.createIndex("ixdemo", "Field1",
{ unique: false, multiEntry: false });
//define new record with users input
var newRecord = {};
newRecord.Field1 = "222";
newRecord.Field2 = "333";
newRecord.Field3 = "444";
var request = objectStore.add(newRecord);
request.onsuccess = function (e) {
var index = objectStore.index('ixdemo');
var range = IDBKeyRange.only("222");
var cursorRequest = index.openCursor();
cursorRequest.onsuccess = function(e) {
var cursor = cursorRequest.result || e.result;
if(cursor) {
alert(JSON.stringify(cursor.value));
cursor.continue();
}
}
}
}
The error message Type Error: cursor is undefined happens because you are using the cursor without checking to see if it's defined. So when cursor.continue() tells IndexedDB to go get the next object in the database, the cursor will be undefined after it exhausts the only object that actually exists.
So you should do something like this. In your code, it would look like:
cursorRequest.onsuccess = function(e) {
var cursor = cursorRequest.result || e.result;
if (cursor) {
alert(cursor.value);
cursor.continue();
}
}
Also, if you know you are only looking for one object (like when you use IDBKeyRange.only), you can just omit the cursor.continue() part:
cursorRequest.onsuccess = function(e) {
var cursor = cursorRequest.result || e.result;
alert(cursor.value);
}
As for your problem With Chrome, I can't help you there as I've only focused on Firefox so far. I would recommend you try the latest development version of Chrome, which actually does support onupgradeneeded along with various other updates, but in my testing so far it's still pretty buggy and code that works in Firefox can fail in Chrome. You might be better off just waiting some time for Chrome to stabilize, if this isn't an urgent project.
You cannot add records while in onupgardeneeded event.
after versing change, you must reopen the database to get new objectStore schema. so there will be two open
current chrome is, i think still old standard, setVersion, onupgardeneeded is never call.
newRecord must have keyPath 'id' or you should not specified keyPath in creating object store.