I am trying to implement this example.
Everything works fine until I attempt to delete a certain item.
Using this:
request.onupgradeneeded = function(event) {
console.log("upgrade", event);
db = event.target.result;
console.log("db", db);
if (!db.objectStoreNames.contains("chatBot")) {
var objectStore = db.createObjectStore("chatBot", {keyPath: "timeStamp", autoIncrement: true});
}
};
and setting up the deletion:
btnDelete.addEventListener("click", function() {
var id, transaction, objectStore, request;
id = document.getElementById("txtID").value;
console.log("id", typeof id);
transaction = db.transaction("people", "readwrite");
objectStore = transaction.objectStore("people");
request = objectStore.delete(id);
request.onsuccess = function(evt) {
console.log("deleted content");
};
}, false);
There is no problem adding items to the indexedDB but for some reason I can't figure out why it can't delete the items.
The id is a string and the objectStore.delete(id) is the correct implementation.
Here is a pastebin of the example
Using Firefox 18
Since you are using autoIncrement key, the key is generated by the user agent. In FF and Chrome, it is integer starting with 1. If you give valid key and convert your id to integer, your code run fine. I tested in both FF and Chrome (dartium). '1' and 1 are different keys according to IndexedDB API key definition.
Another issue IndexedDB API design. The delete methods always return to success event handler with undefined as result whether given key was deleted or not. So it is very difficult to debug. I think it should return number of deleted records at least.
[Edit] mod code: http://pastebin.com/mLpU0VfP
[Edit... also] Notice the + which converts the string to an integer
request = db.transaction("people", "readwrite").objectStore("people").delete(+id);
Related
I'm currently running an IndexedDB with 3 ObjectStores and the following onupgradeneeded function.
var openRequest = indexedDB.open("DB_v3", 1);
openRequest.onupgradeneeded = function(e) {
var thisDB = e.target.result;
if(!thisDB.objectStoreNames.contains("ObjectStore1")) {
var objectStore = thisDB.createObjectStore("ObjectStore1", {autoIncrement:true});
objectStore.createIndex("name", "name", {unique:true});
}
if(!thisDB.objectStoreNames.contains("ObjectStore2")) {
//create objectstore & index as above
}
if(!thisDB.objectStoreNames.contains("ObjectStore3")) {
//create objectstore & index as above
}
}
What I want to do now is add a 4th ObjectStore.
That for I change the version and add a 4th entry in onupgradeneeded.
var openRequest = indexedDB.open("DB_v4", 1);
openRequest.onupgradeneeded = function(e) {
//same as above
if(!thisDB.objectStoreNames.contains("ObjectStore4")) {
//create objectstore & index as above
}
}
All this works fine, but the problem is, as soon as the onupgradeneeded gets called the already existing ObjectStores lose all their data.
How does the onupgradeneeded need to look in order to keep the data of the already existing ObjectStores?
indexedDB.open("DB_v3", 1) opens a database named DB_v3 at version 1.
indexedDB.open("DB_v4", 1) opens a database named DB_v4 at version 1.
Databases with different names are completely separate. You're not upgrading, you're just creating two distinct databases. If you want to upgrade an existing database, keep the name the same and increment the version. Then the old data will still be there.
It appears that the Safari and iPhone web browsers are incapable of allowing the user to create different object stores from separate transactions. This is even the case when the user closes the database, increments the version number and then uses createObjectStore() within the onupgradedneeded callback.
Is there a workaround?
For example, visit http://bl.ocks.org/redgeoff/1dea140c52397d963377 in Safari and you'll get an alert with the "AbortError" when Safari attempts to create the 2nd object store.
For convenience, here is the same snippet of code:
var idb = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB
|| window.msIndexedDB;
// Generate a unique db name as IndexedDB is very delicate and we want our test
// to focus on a new DB
var dbName = 'mydb' + '_' + (new Date()).getTime() + '_'
+ Math.round(1000000*Math.random());
var db = null;
var version = 1;
var open = function (version, onSuccess, onUpgradeNeeded) {
var request = null;
if (version) {
request = idb.open(dbName, version);
} else { // 1st time opening?
request = idb.open(dbName);
}
request.onupgradeneeded = function () {
if (onUpgradeNeeded) {
onUpgradeNeeded(request);
}
};
request.onsuccess = function () {
db = request.result;
if (onSuccess) {
onSuccess(request);
}
};
request.onerror = function () {
console.log('error=', request.error);
alert('error=' + JSON.stringify(request.error));
};
};
var createObjectStore = function (name, callback) {
db.close(); // synchronous
version++; // increment version to trigger onupgradeneeded
open(version, callback, function (request) {
request.result.createObjectStore(name, {
keyPath: 'id'
});
});
};
// NOTE: we could create the first store when opening the DB for the first time, but we'll keep
// things simple and reuse our createObjectStore code for both object stores
open(null, function () {
createObjectStore('store1', function () {
createObjectStore('store2', function () {
console.log('done creating both stores');
});
});
});
I tried using a sleep of 2 secs after the DB is closed and reopened and that doesn't appear to work. If there is no workaround then this essentially means that you cannot use the IndexedDB implementation in Safari to dynamically create object stores, which means that you need to know all your object stores before creating a DB.
Unless I am mistaken and someone has a workaround, the best way to dynamically add object stores is to implement a db-per-object-store design. In other words, you should create a new database whenever you need to create a new object store.
Another good option is to use https://github.com/axemclion/IndexedDBShim to emulate IndexedDB with WebSQL.
I have a data like this:
"customers": {
"aHh4OTQ2NTlAa2xvYXAuY29t": {
"customerId": "xxx",
"name": "yyy",
"subscription": "zzz"
}
}
I need to retrive a customer by customerId. The parent key is just B64 encoded mail address due to path limitations. Usually I am querying data by this email address, but for a few occasions I know only customerId. I've tried this:
getCustomersRef()
.orderByChild('customerId')
.equalTo(customerId)
.limitToFirst(1)
.once('child_added', cb);
This works nicely in case the customer really exists. In opposite case the callback is never called.
I tried value event which works, but that gives me whole tree starting with encoded email address so I cannot reach the actual data inside. Or can I?
I have found this answer Test if a data exist in Firebase, but that again assumes that you I know all path elements.
getCustomersRef().once('value', (snapshot) => {
snapshot.hasChild(`customerId/${customerId}`);
});
What else I can do here ?
Update
I think I found solution, but it doesn't feel right.
let found = null;
snapshot.forEach((childSnapshot) => {
found = childSnapshot.val();
});
return found;
old; misunderstood the question :
If you know the "endcodedB64Email", this is the way.:
var endcodedB64Email = B64_encoded_mail_address;
firebase.database().ref(`customers/${endcodedB64Email}`).once("value").then(snapshot => {
// this is getting your customerId/uid. Remember to set your rules up for your database for security! Check out tutorials on YouTube/Firebase's channel.
var uid = snapshot.val().customerId;
console.log(uid) // would return 'xxx' from looking at your database
// you want to check with '.hasChild()'? If you type in e.g. 'snapshot.hasChild(`customerId`)' then this would return true, because 'customerId' exists in your database if I am not wrong ...
});
UPDATE (correction) :
We have to know at least one key. So if you under some circumstances
only know the customer-uid-key, then I would do it like this.:
// this is the customer-uid-key that is know.
var uid = firebase.auth().currentUser.uid; // this fetches the user-id, referring to the current user logged in with the firebase-login-function
// this is the "B64EmailKey" that we will find if there is a match in the firebase-database
var B64EmailUserKey = undefined;
// "take a picture" of alle the values under the key "customers" in the Firebase database-JSON-object
firebase.database().ref("customers").once("value").then(snapshot => {
// this counter-variable is used to know when the last key in the "customers"-object is passed
var i = 0;
// run a loop on all values under "customers". "B64EmailKey" is a parameter. This parameter stores data; in this case the value for the current "snapshot"-value getting caught
snapshot.forEach(B64EmailKey => {
// increase the counter by 1 every time a new key is run
i++;
// this variable defines the value (an object in this case)
var B64EmailKey_value = B64EmailKey.val();
// if there is a match for "customerId" under any of the "B64EmailKey"-keys, then we have found the corresponding correct email linked to that uid
if (B64EmailKey_value.customerId === uid) {
// save the "B64EmailKey"-value/key and quit the function
B64EmailUserKey = B64EmailKey_value.customerId;
return B64UserKeyAction(B64EmailUserKey)
}
// if no linked "B64EmailUserKey" was found to the "uid"
if (i === Object.keys(snapshot).length) {
// the last key (B64EmailKey) under "customers" was returned. e.g. no "B64EmailUserKey" linkage to the "uid" was found
return console.log("Could not find an email linked to your account.")
}
});
});
// run your corresponding actions here
function B64UserKeyAction (emailEncrypted) {
return console.log(`The email-key for user: ${auth.currentUser.uid} is ${emailEncrypted}`)
}
I recommend putting this in a function or class, so you can easily call it up and reuse the code in an organized way.
I also want to add that the rules for your firebase must be defined to make everything secure. And if sensitive data must be calculated (e.g. price), then do this on server-side of Firebase! Use Cloud Functions. This is new for Firebase 2017.
I am trying to get the hang of using indexedDB to store data client side.
consider the following code:
function queryURL(message, sender)
{
chrome.contextMenus.removeAll();
var openRequest = indexedDB.open("Tags",1);
openRequest.onsuccess = function(event){
var queryURL = message['host'];
var db = event.target.result;
var objectStore = db.transaction("domains").objectStore("domains");
var query = objectStore.get(queryURL);
query.onsuccess = function(event){
alert(query.result);
delete query.result["domain"];
createMenuItems(query.result);
available_commands=request.result;
};
db.onerror = function(event){
console.log("an error bubbled up during a transaction.");
};
};
openRequest.onerror = function(event){
console.log("error opening DB");
};
}
I do not fully understand what should be happening in the query.
The result is the same whether or not the key that is queried for is in the database:
query.onsuccess() runs and query.result is undefined so the
code errors and exits as soon as I try to delete a key from
query.result.
If the key is not found, query.onsuccess() should not be
running, correct?
If the key is found, query.result should hold the object that
corresponds to that key, correct?
In case it helps, here is the code that I used to initialize the database:
const db_name="Tags";
var request = window.indexedDB.open(db_name, 1);
var tags = [
//codes: 0 - markdown wrap tag
// 1 - HTML wrap tag
// 2 - single tag
{ domain: "www.youtube.com",
bold:["*",0],
strikethrough:["-",0],
italic:["_",0]
},
{ domain: "www.stackoverflow.com",
bold:["<strong>",1],
italic:["<em>",1],
strikethrough:["<del>",1],
superscript:["<sup>",1],
subscript:["<sub>",1],
heading1:["<h1>",1],
heading2:["<h2>",1],
heading3:["<h3>",1],
blockquote:["<blockquote>",1],
code:["<code>",1],
newline:["<br>",2],
horizontal:["<hr>",2]
}
];
request.onerror = function(event) {
alert("Error opening the database");
};
request.onupgradeneeded = function(event) {
var db = event.target.result;
alert("I'm doing stuff!");
var objectStore = db.createObjectStore("domains", {keyPath: "domain" });
objectStore.createIndex("domain", "domain", { unique: true });
objectStore.transaction.onComplete = function(event) {
var domanStore=db.transaction("domains","readwrite").objectStore("domains");
for(var i in tags)
{
domainStore.add(tags[i]);
}
}
};
Here are some links to the resources I am using:
Using IndexedDB
IDBObjectStore
IDBRequest
Finding out that the result is empty or undefined is a successful query. So yes, you get onsuccess call with result === undefined.
onerror is only reserved for when something breaks, e.g. you supplied an invalid key.
From IDBObjectStore.get docs:
Note: This method produces the same result for: a) a record that doesn't exist in the database and b) a record that has an undefined value. To tell these situations apart, call the openCursor() method with the same key. That method provides a cursor if the record exists, and no cursor if it does not.
Yes. It is even more confusing when delete method return success, even if no record is deleted.
Since request error event is cancellable bubbling event, it is not feasible to invoke to error callback even if no record is found. If request is on error and error is not prevented, its transaction will be aborted and indexedDB.onerror will be called as well. So invoking success with undefined result is still better than invoking error.
I have tried to get some information from W3C regarding the update of an objectStore item in a indexedDB database, but with not so much susccess.
I found here a way to do it, but it doesn't really work for me.
My implementation is something like this
DBM.activitati.edit = function(id, obj, callback){
var transaction = DBM.db.transaction(["activitati"], IDBTransaction.READ_WRITE);
var objectStore = transaction.objectStore("activitati");
var keyRange = IDBKeyRange.only(id);
objCursor = objectStore.openCursor(keyRange);
objCursor.onsuccess = function(e){
var cursor = e.target.result;
console.log(obj);
var request = cursor.update(obj);
request.onsuccess = function(){
callback();
}
request.onerror = function(e){
conosole.log("DBM.activitati.edit -> error " + e);
}
}
objCursor.onerror = function(e){
conosole.log("DBM.activitati.edit -> error " + e);
}
}
I have all DBM.activitati.(add | remove | getAll | getById | getByIndex) methods working, but I can not resolve this.
If you know how I can manage it, please, do tell!
Thank you!
Check out this jsfiddle for some examples on how to update IDB records. I worked on that with another StackOverflower -- it's a pretty decent standalone example of IndexedDB that uses indexes and does updates.
The method you seem to be looking for is put, which will either insert or update a record if there are unique indexes. In that example fiddle, it's used like this:
phodaDB.indexedDB.addUser = function(userObject){
//console.log('adding entry: '+entryTxt);
var db = phodaDB.indexedDB.db;
var trans = db.transaction(["userData"],IDBTransaction.READ_WRITE);
var store = trans.objectStore("userData");
var request = store.put(userObject);
request.onsuccess = function(e){
phodaDB.indexedDB.getAllEntries();
};
request.onerror = function(e){
console.log('Error adding: '+e);
};
};
For what it's worth, you've got some possible syntax errors, misspelling "console" in console.log as "conosole".
A bit late for an answer, but possible it helps others. I still stumbled -as i guess- over the same problem, but it's very simple:
If you want to INSERT or UPDATE records you use objectStore.put(object) (help)
If you only want to INSERT records you use objectStore.add(object) (help)
So if you use add(object), and a record key still exists in DB, it will not overwritten and fires error 0 "ConstraintError: Key already exists in the object store".
If you use put(object), it will be overwritten.
this is case of update infos of an user object
var transaction = db.transaction(["tab_user"], "readwrite");
var store = transaction.objectStore("tab_user");
var req = store.openCursor();
req.onerror = function(event) {
console.log("case if have an error");
};
req.onsuccess = function(event) {
var cursor = event.target.result;
if(cursor){
if(cursor.value.idUser == users.idUser){//we find by id an user we want to update
var user = {};
user.idUser = users.idUser ;
user.nom = users.nom ;
var res = cursor.update(user);
res.onsuccess = function(e){
console.log("update success!!");
}
res.onerror = function(e){
console.log("update failed!!");
}
}
cursor.continue();
}
else{
console.log("fin mise a jour");
}
}
I'm a couple of years late, but thought it'd be nice to add my two cents in.
First, check out BakedGoods if you don't want to deal with the complex IndexedDB API.
It's a library which establishes a uniform interface that can be used to conduct storage operations in all native, and some non-native client storage facilities. It also maintains the flexibility and options afforded to the user by each. Oh, and it's maintained by yours truly :) .
With it, placing one or more data items in an object store can be as simple as:
bakedGoods.set({
data: [{key: "key1", value: "value1"}, {key: "key2", value: "value2"}),
storageTypes: ["indexedDB"],
complete: function(byStorageTypeResultDataObj, byStorageTypeErrorObj){}
});
Now to answer the actual question...
Lets begin by aggregating the valuable information spread across the existing answers:
IDBObjectStore.put() adds a new record to the store, or updates an existing one
IDBObjectStore.add() adds a new record to the store
IDBCursor.update() updates the record at the current position of the cursor
As one can see, OP is using an appropriate method to update a record. There are, however, several things in his/her code, unrelated to the method, that are incorrect (with respect to the API today at least). I've identified and corrected them below:
var cursorRequest = objectStore.openCursor(keyRange); //Correctly define result as request
cursorRequest.onsuccess = function(e){ //Correctly set onsuccess for request
var objCursor = cursorRequest.result; //Get cursor from request
var obj = objCursor.value; //Get value from existing cursor ref
console.log(obj);
var request = objCursor.update(obj);
request.onsuccess = function(){
callback();
}
request.onerror = function(e){
console.log("DBM.activitati.edit -> error " + e); //Use "console" to log :)
}
}
cursorRequest.onerror = function(e){ //Correctly set onerror for request
console.log("DBM.activitati.edit -> error " + e); //Use "console" to log :)
}