First off, my thanks to Parashuram Narasimhan, the author of the IndexedDB Polyfill and the Jquery-IndexedDB plugin, for responding to my question by pointing me in the direction of his test-suites page, and specifically, to the "ObjectStore update" test function.
For those wanting to get straight to the answer to my initial question, scroll to the end. However I thought it might help others (learning like me) if I explained how I got to a working solution...
To start off, I stripped the test-suite code into a simple method that could be triggered by an html anchor. (note that I was initially confused by the fact that the original "ObjectStore update" test function first 'adds' an object before updating it. Maybe that function would be better named "ObjectStore create data then update" ?)
Anyway, here's the relevant code from my reworked version (where '111' is the key I want to update, and the changes to be made are hard coded within the function):
$("a#modify").click(function(e) {
ObjectStoreUpdate(DB.OBJECT_STORE_1,111);
e.preventDefault();
});
function ObjectStoreUpdate(table,key){
// get object by key (return a reference to it)
$.indexedDB(DB.NAME).objectStore(table).get(key).then(function(obj){
console.log("Got the object to be updated:", obj);
// make changes
obj["String"] = "Resampled " + new Date();
obj["Boolean"] = !obj["Boolean"]; // toggle true/false
obj["modified"] = Math.floor((Math.random()*100)+1); // random number 1 to 100
var newVal = obj;
$.indexedDB(DB.NAME).objectStore(table).put(newVal, key).then(function(){
$.indexedDB(DB.NAME).objectStore(table).get(key).then(function(val){
console.log("Got back the updated values:", val);
}, function(err, e){
console.log("Could not get back updated data");
});
}, function(err, es){
console.log("Could not update data");
});
}, function(err, e){
console.log("Could not select by key:", key);
});
};
Note that unlike my inital attempt (in my question), it no longer uses verbose transactions syntax but instead imploys the shorthand provided by the plugin api.
From there it wasn't too difficult to implement this and rewrite my original code (again, the changed property 'last_updated' is just hard-coded):
updatingLocalStoreObject(table, key){
var deferredReady = $.Deferred();
// get object by key (return a reference to it)
$.indexedDB(_DBNAME).objectStore(table).get(key).then(function(obj){
console.log("Got the object to be updated:", obj);
// make changes
var timestamp = new Date();
timestamp = Math.round(timestamp.getTime()/1000);
obj["last_updated"] = timestamp;
var newVal = obj;
$.indexedDB(_DBNAME).objectStore(table).put(newVal, key).then(function(){
$.indexedDB(_DBNAME).objectStore(table).get(key).then(function(val){
console.log("Got back the updated values:", val);
deferredReady.resolve();
}, function(err, e){
console.log("Could not get back updated data", err, e);
deferredReady.reject();
});
}, function(err, es){
console.log("Could not update data", err, es);
deferredReady.reject();
});
}, function(err, e){
console.log("Could not select by key:", key);
deferredReady.reject();
});
return deferredReady.promise();
}
Unfortunately this didn't work. Unlike the refactored test-suite version I was getting an error in the console:
Could not update data:
"The object store uses in-line keys and the key parameter was provided."
Turns out that the problematic line was the 'put' request:
$.indexedDB(_DBNAME).objectStore(table).put(newVal, key).then(function(){
This issue has been asked before on StackOverflow and the comments point to a useful resource on how and when to use keys in indexeddb
So the solution (for my database structure, which uses a KeyPath) was just to omit the key:
$.indexedDB(_DBNAME).objectStore(table).put(newVal).then(function(){
So finally, here's the full working code, which answers both parts of my original question by supporting a complex 'updateObject':
//Get current time
var timestamp = new Date();
timestamp = Math.round(timestamp.getTime()/1000);
var updateObject={current_level:999, pass:11, last_updated : timestamp};
//do update
updatingLocalStoreObject('userdata','k1078511',updateObject)
.done(function(){
console.log("did update");
loadFromObjectStore("userdata"); // reloads HTML view
})
.fail(function() {
console.log("failed update");
});
function updatingLocalStoreObject(table,key, updateObject){
var deferredReady = $.Deferred();
// get object by key (return a reference to it)
$.indexedDB(_DBNAME).objectStore(table).get(key).then(function(obj){
console.log("Got the object to be updated:", obj);
// make changes
$.each(updateObject, function(updateKey,updateVal){
if (updateKey in obj){
obj[updateKey] = updateVal;
}
});
var newVal = obj;
$.indexedDB(_DBNAME).objectStore(table).put(newVal).then(function(){
$.indexedDB(_DBNAME).objectStore(table).get(key).then(function(val){
console.log("Got back the updated values:", val);
deferredReady.resolve();
}, function(err, e){
console.log("Could not get back updated data", err, e);
deferredReady.reject();
});
}, function(err, es){
console.log("Could not update data", err, es);
deferredReady.reject();
});
}, function(err, e){
console.log("Could not select by key:", key);
deferredReady.reject();
});
return deferredReady.promise();
}
Some final thoughts... this works by first getting a reference to the whole object at the selected keyPath and then making changes to the properties in the 'updateObject' I've defined - essentially it iterates through the entire indexxeddb object and only updates those properties that match, before then updating the entire object) - this is fine on small objects/datasets but would be inefficient if lots of properties and data were present. Have I missed something? Is there no way to directly alter a single property?