-1

I'm using the jquery-indexed-db jquery plugin for a simple web application that relies on Indexeddb for client-side storage.

I've successfully modified the example code to store and read data, but cannot work out how to update a stored object.

Here's the DB structure (with the objectStore I'm trying to alter highlighted in blue) enter image description here

To start off, I want to update the value of 'last_updated' for a specific user (keyPath: "username", Key value: "k1078511", this being the unique identifier for that objectStore)

Here's the JavaScript code I'm using:

//Get current time
    var timestamp = new Date();
    timestamp = Math.round(timestamp.getTime()/1000);
    var updateObject={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, keyValue, updateObject){
    var deferredReady = $.Deferred();

    var mode =  1; // READ_WRITE as per plugin API
    // var mode =  "readwrite"; // using IndexedDB Specification

    $.indexedDB('AppTest').transaction(table, mode).then(function(){
        console.log("Transaction 'on "+table+"' completed, all data updated");
        deferredReady.resolve();
    }, function(err, e){
        console.log("Transaction 'update "+table+"' NOT completed", err, e);
        deferredReady.reject();
    }, function(transaction){
        var userdata = transaction.objectStore(table);
        console.log('doing put transaction');
        console.log(updateObject);
        userdata.put(updateObject,keyValue);
    });
    return deferredReady.promise();
}

This returns the following in the console (with relevant log items highlighted): enter image description here

'TypeError' as the error message seems odd to me. The stored javascript object does hold an integer for that index (last_updated: 0)

After some searching, I found that some people using this plugin are using var mode = "readwrite" (as per the IndexedDB Specification) rather than strictly adhering to the plugin API. (see comments in code above.

This apparently works better according to the console, as it runs the transaction:

enter image description here

but in fact nothing changes in the Indexeddb store, the HTML view is the same and checking in the browser's Resources tool also shows no changes in the database.

Can anyone help? I'd very much appreciate a working code example.

Eventually I'd like to be able to call my function using a more complex 'updateObject', for example:

var updateObject={current_level: 5, passes: 7, last_updated : timestamp};
Community
  • 1
  • 1
baroquedub
  • 952
  • 1
  • 10
  • 13

1 Answers1

0

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?

Community
  • 1
  • 1
baroquedub
  • 952
  • 1
  • 10
  • 13