4
\$\begingroup\$

I created this observable storage thing in JavaScript (partly for fun, but also because it came in handy and got bigger without me noticing it).

The repository can be found here.

(function(window, undefined){
    "use strict";

    var eagle = window.eagle = (function() {

        var storage = {
            feathers : {},
            observers : {}
        }

        function storeFeather(name, data) {
            var action;
            if ( (name in storage.feathers) ) {
                action = 'update';
            } else {
                action = 'insert';
            }
            //for now, copletly overwrite the shit out of it
            storage.feathers[name] = data;

            notifyObservers(name, action);
        }

        function retrieveFeather(name) {
            return storage.feathers[name];
        }

        function destroyFeather(name) {
            delete storage.feathers[name];
            notifyObservers(name, 'destroy');
        }

        function notifyObservers(observerTarget, action) {
            if ( !(observerTarget in storage.observers) ) {
                return;
            }

            if ( !(action in storage.observers[observerTarget]) ) {
                return;
            }

            var observers = storage.observers[observerTarget][action];

            for ( var i=0, len=observers.length; i<len; i++ ) {
                observers[i](storage.feathers[observerTarget], action);
            }
        }

        function registerObserver(observerTarget, action, task) {
            if ( !(observerTarget in storage.observers) ) {
                storage.observers[observerTarget] = [];
            }

            if ( action instanceof Array ) {
                for ( var i=0, len=action.length; i<len; i++ ) {
                    registerObserverForAction(observerTarget, action[i], task);
                }
            } else if ( typeof action == 'string' ) {
                registerObserverForAction(observerTarget, action, task);
            }
        }

        function registerObserverForAction(observerTarget, action, task) {
            if ( !(action in storage.observers[observerTarget]) ) {
                storage.observers[observerTarget][action] = [];
            }

            storage.observers[observerTarget][action].push(task);
        }

        return {
            observe : function() {
                switch( arguments.length ) {
                    case 2:
                        return registerObserver(
                            arguments[0],
                            ['update','insert','destroy'],
                            arguments[1]
                        );
                    break;

                    case 3:
                        return registerObserver(
                            arguments[0],
                            arguments[1],
                            arguments[2]
                        );
                    break;

                    default:
                        throw "Invalid arguments length";
                    break;
                }
            },

            store : function() {
                switch( arguments.length ) {
                    case 2:
                        return storeFeather(arguments[0], arguments[1]);
                    break;

                    default:
                        throw "Invalid arguemnts length";
                    break;
                }
            },

            retrieve : function() {
                switch( arguments.length ) {
                    case 1:
                        return retrieveFeather(arguments[0]);
                    break;

                    default:
                        throw "Invalid arguemnts length";
                    break;
                }
            },

            destroy : function() {
                switch( arguments.length ) {
                    case 1:
                        return destroyFeather(arguments[0]);
                    break;

                    default:
                        throw "Invalid arguemnts length";
                    break;
                }
            }
        }

    })();
})(this);

Usage:

Store

Store the data in the eagle. Data is stored as a so called Feather.

eagle.store('my-data', {
    actual : 'data'
});

Retrieve

Retrieve stored data from the eagle.

eagle.retrieve('my-data'); // {actual:'data'}

Destroy

completly remove data from the eagle. Uses delete internally.

eagle.destroy('my-data');

Observer

If we need to keep track of what is happening to our data stored inside the eagle. We can add observers. Observers are notified when something happens to the Feather.

eagle.observe('my-data', function(feather, action) {
    console.log('something happened to my-model');
    console.log('That something is '+action);
    console.log('The data now is: '+feather);
});

If you would like to observe only a certain action. This can be done bypassing an extra parameter:

eagle.observe('my-data', 'update', function(feather, action) {
    console.log('something happened to my-model');
    console.log('That something is '+action);
    console.log('The data now is: '+feather);
});
\$\endgroup\$
  • \$\begingroup\$ @Jamal I don't think your edit was correct. Some fun should be allowed! \$\endgroup\$ – Pinoniq Jul 31 '14 at 7:32
  • \$\begingroup\$ Just my 2 cents: look into Object.defineProperty to set methods/properties that should not be editable by the user... \$\endgroup\$ – Elias Van Ootegem Jul 31 '14 at 11:09
  • \$\begingroup\$ Problem was that IE8 only allows that on DOM elements. And I am lucky enough to have to support IE8 on the project I am using it in :p \$\endgroup\$ – Pinoniq Jul 31 '14 at 11:49
  • \$\begingroup\$ @Pinoniq: That was mostly noise (especially as a question), and a post should be free from it. If you'd like to do this in the proper place, you may join us in chat. \$\endgroup\$ – Jamal Jul 31 '14 at 15:34
  • \$\begingroup\$ Follow-up question \$\endgroup\$ – 200_success Oct 7 '14 at 18:08
4
\$\begingroup\$

A few comments:

  • You probably want to use foo.hasOwnProperty(bar) instead of bar in foo. What happens if someone adds feather called "constructor?"

  • Using window for the global object feels wrong. What if someone runs this in a non-browser environment?

  • The undefined parameter is never used.

  • You've got your code nested in two IIFEs where one would do.

  • Throw errors, not strings. Or better yet, don't throw anything, because...

  • Checking for "invalid argument lengths" adds bloat and probably is not necessary. If you document your functions properly and people try to call them with an invalid argument list, that's user error. User error is not something your program should have to worry about* (but good documentation is).

  • observerTarget can probably just be target. I think observers[target] is a bit easier to mentally parse than observers[observerTarget]; the latter feels redundant and makes me wonder what other kind of target there might be.

  • I see no reason to have feathers and observers as properties of another object; these can just be variables.

These are mostly nitpicks, all in all the code looks good. For what it's worth, here's how I might have written it:

(function (global) {
    'use strict';

    var feathers = {},
        observers = {};

    function storeFeather(name, data) {
        var action = feathers.hasOwnProperty(name) ? 'update' : 'insert';

        feathers[name] = data;
        notifyObservers(name, action);
    }

    function retrieveFeather(name) {
        return feathers[name];
    }

    function destroyFeather(name) {
        delete feathers[name];
        notifyObservers(name, 'destroy');
    }

    function notifyObservers(target, action) {
        if (!observers.hasOwnProperty(target)) {
            return;
        }

        if (!observers[target].hasOwnProperty(action)) {
            return;
        }

        var reactors = observers[target][action];

        for (var i = 0, len = reactors.length; i < len; i++) {
            reactors[i](feathers[target], action);
        }
    }

    function registerObserverForAction(target, action, task) {
        if (!observers[target].hasOwnProperty(action)) {
            observers[target][action] = [];
        }

        observers[target][action].push(task);
    }

    function registerObserver(target, action, task) {
        if (!observers.hasOwnProperty(target)) {
            observers[target] = [];
        }

        if (arguments.length < 3) {
            task = action;
            action = ['update', 'insert', 'destroy'];
        }

        if (action instanceof Array) {
            for (var i = 0, len = action.length; i < len; i++) {
                registerObserverForAction(target, action[i], task);
            }
        } else if (typeof action == 'string') {
            registerObserverForAction(target, action, task);
        }
    }

    var eagle = {
        observe: registerObserver,
        store: storeFeather,
        retrieve: retrieveFeather,
        destroy: destroyFeather
    };

    global.eagle = eagle;

})(this);

* The user in question being another programmer, of course. End-user error is always something to worry about.

\$\endgroup\$
  • \$\begingroup\$ +1. Particularly for User [i.e. programmer] error is not something your program should have to worry about* (but good documentation is) \$\endgroup\$ – Flambino Jul 30 '14 at 19:50
  • \$\begingroup\$ some good comments. About the window object. I use the first IIFE to pass in the window object ((this) at the bottom). So the window inside my first IIFE is whatever enviroment this code is being executed in. The rest of the comments, really liked them \$\endgroup\$ – Pinoniq Jul 31 '14 at 7:29
  • 1
    \$\begingroup\$ @Pinoniq, about the window thing, I know, I just prefer to name the local variable something like "global," it seems more fitting than "window" (because it might not always be a window). \$\endgroup\$ – Dagg Jul 31 '14 at 13:04
4
\$\begingroup\$

Pretty nice! I have only a few comments:

  1. You have a typo: "arguemnts" in your throw statements :)

  2. I'd avoid all the extra logic in the returned object. I'd prefer the functions to be doing all the argument handling themselves. I do understand why you'd split up the logic, but at the same time you're, well, splitting up the logic.

  3. There should probably be a way to remove observers...

  4. You could consider more conventional naming, like on or addEventListener for observers. You say "observer", but I'd rather say "event handler", since it's not total value observing.
    If I do:

    var data = { foo: "bar" };
    eagle.observe("data", dataChanged);
    eagle.store("data", data); // will call dataChanged
    data.foo = "baz";          // will *not* call dataChanged
    

    It's a minor thing, and as much a question of personal preference, but I find the word "observe" to imply more than what's actually happening here.

\$\endgroup\$
  • \$\begingroup\$ Yeah, dataChanged will indeed only becalled when 'saving' data via store. This is how I wanted it to be :p It was by design ;) But it might be a cool thing to have a save() function or so. I might encapsulate the data in a Feather function or so with a save() method. will see :) \$\endgroup\$ – Pinoniq Jul 31 '14 at 7:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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