6
\$\begingroup\$

The goal is to

  • make an object that persists upon page reloads,
  • have an interface as close as possible to the Object class.
function PersistentObject(key,initial_value)
//use for(..in data.keys()) instead of for(..in data)
{
  var ret;
  if(localStorage[key]) ret=JSON.parse(localStorage[key]);
  if(!ret) ret = initial_value;
  if(!ret) ret = {};
  //JSON.stringify ignores put and keys functions
  ret.put=function() { localStorage[key]=JSON.stringify(this) };
  ret.keys=function()
  {
    var res = {};
    for(var prop in this) if(this[prop] !== this.put && this[prop] !== this.keys)
      res[prop]=true;
    return res;
  };
  return ret;
}

The code is used as follows:

var myPersistentObject = new PersistentObject('myPersistentObject');
var myPersistentArray  = new PersistentObject('myPersistentArray',[]);

Unfortunately there is AFAIK no way to get the name of the variable and therefore the name of the variable needs to be passed as first argument.

Also unfortunate is that the function keys needs to be called in order to iterate over the object. I'm aware of the defineProperty function that allow to add a propoerty that is not enumerable but AFAIK cross browser compability is an issue with defineProperty.

\$\endgroup\$

3 Answers 3

2
\$\begingroup\$

It is impossible to know the variable name without passing it separately because the name is not part of the object itself, but only one (of potentially many) signs pointing to that object. Expecting the object to know that variable name is like expecting a building to know which road you drove in on. Besides, specifying the localStorage key separately is a good thing - it allows you to refer to the same key using different names in different parts of the program.

You can get around having your methods show up in for-in loops by using getters instead of properties or prototypes. If you're concerned about compatibility, you can fallback to __defineGetter__.

Here's what I came up with.

function localObject(key) {
    var _this = JSON.parse(localStorage[key] || '{}');
    Object.defineProperty(_this, 'save', {
        get: function () {
            return function () {
                localStorage[key] = JSON.stringify(this);
            };
        }
    });
    return _this;
}

The new is optional - you can write both

var scores = new localObject('highscores'); // and
var scores = localObject('highscores');`.

While the above solution works, it is somewhat inefficient and messy. This stems from the fact that you're trying to get javascript to work the way you want, when what you should be doing is trying to understand how javascript wants to work. Each language has a style, even a personality, and if you're flexible enough to go with it then your code will be shorter, faster, and cleaner. Here's how I would abstract away my JSONification.

function local(key, value) {
    if (value === undefined) {
        return key in localStorage ? JSON.parse(localStorage[key]) : undefined;
    } else {
        localStorage[key] = JSON.stringify(value);
        return value;
    }
}

Now we can write code like this:

var score = local('highscore');
// use score like a normal variable, because it is one. then, eventually:
local('highscore', score);
\$\endgroup\$
1
\$\begingroup\$

I think you are making things more complicated than they need to be. Wouldn't it be simpler to use a library such as store.js and just do:

var o = store.get('someObject');
if (!o) o = new SomeClass();

Then store it back:

store.set('someObject', o);

It takes three lines and a rather clean code.

\$\endgroup\$
2
  • \$\begingroup\$ It sounds like brillout is satisfied with the compatibility of localStorage, so there's no need for such a large and complex function. \$\endgroup\$
    – delete me
    May 15, 2012 at 8:12
  • \$\begingroup\$ store.js is actually only 5KB with indeed the added benefit of being cross-browser. \$\endgroup\$
    – laurent
    May 15, 2012 at 9:33
0
\$\begingroup\$

Another approach to take with this is to use a get accessor to trigger a save to localStorage. For instance, if you have an object mem, you can set up an accessor for a data property which will return a backing object and save the updated backing object to localStorage via a setTimeout(..., 0) call.

You could then set a value like the following and it would be accessible on page-reload:

mem.data.highscore = 9999;

One way to set this up:

window.mem = (function() {

    return createChan('mem_default');

    function createChan(chanKey) {

        var key = chanKey,
            data = JSON.parse(localStorage[chanKey] || '{}'),
            sched = schedSave,
            api = {
                save: doSave,
                suppressSave: noSave,
                clear: clear,
                createChan: createChan
            },
            saveTimeout;

        Object.defineProperty(api, "data", {
            get: function() {
                sched();
                return data; 
            },
            set: function(value) {
                if (value != data) {
                    data = validateData(value); 
                    sched();
                }
            },
            enumerable : true
        });

        return api;

        function schedSave() {

            sched = noop;
            if (saveTimeout) {
                return;
            }
            saveTimeout = setTimeout(doSave, 0);
        }

        function doSave() {

            if (saveTimeout) {
                clearTimeout(saveTimeout)
            }
            saveTimeout = undefined;
            localStorage[key] = JSON.stringify(data);
            sched = schedSave;
        }

        function noSave() {
            if (saveTimeout) {
                clearTimeout(saveTimeout)
            }
            sched = noop;
            saveTimeout = setTimeout(function() {
                sched = schedSave;
                saveTimeout = undefined;
            }, 0);
        }

        function clear() {
            // TODO
        }
    }

    function validateData(value) {

        var newData;

        switch (typeof value) {

            case 'string':
            case 'boolean':
            case 'number':
                return {value: value};

            case 'function':
                newData = {};
                for (var p in value) {
                    if (value.hasOwnProperty(p)) {
                        newData[p] = value[p];
                    }
                }
                return newData;

            case 'object':
                return value ? value : {};
        }
        return {};
    }

    function noop() {}

})();

Here is a jsFiddle demo.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.