3
\$\begingroup\$

I wrote a small piece of code for observing changes on JSON object. Whenever there is a change in Object, it should trigger all the handlers with simplistic information about data change.

I am looking for improvements in the code. As I am struck with this pattern, could not think of better ideas of it. Please help.

How I am using this:

 var reactiveData = new Observer({a: [1,2, {b: 10}]});
 reactiveData.observe('a.3', function() {
    console.log('Changed');
 });

Implementation:

 var Observer = (function() {
    function Observer(data) {
        this.data = data;
        this.handlers = {};
    }
    function clone(obj) { 
        return JSON.parse(JSON.stringify(obj)); 
    }
    function setValue(object, path, value) {
        var a = path, o = object;
        if(!Array.isArray(path)) {
            a = path.split('.');
        }
        for (var i = 0; i < a.length - 1; i++) {
            var n = a[i];
            if (n in o) {
                o = o[n];
            } else {
                o[n] = {};
                o = o[n];
            }
        }
        o[a[a.length - 1]] = value;
    }
    function getValue(object, path, getTrace) {
        var o = object, a = path, trace = [];
        if(!Array.isArray(path)) {
            path = path.replace(/\[(\w+)\]/g, '.$1');
            path = path.replace(/^\./, '');
            a = path.split('.');
        } else {
            a = path.slice(0);
        }
        while (a.length) {
            var n = a.shift();
            if (n in o) {
                o = o[n];
                if(getTrace) trace.push(o);
            } else {
                return getTrace ? trace : undefined;
            }
        }
        return getTrace ? trace : o;
    }
    function triggerHandlers(path, eventData) {
        var that = this, handlers = this.handlers;
        if(Array.isArray(path)) path = path.join('.');
        var pathHandlers = this.handlers[path] || [];
        pathHandlers.forEach(function(handler) {
            handler(eventData);
        });
    }
    var prototype = {
        constructor: Observer
    };
    prototype.setValue = function(path, value, options) {
        options = options || {};
        var trace = this.getValue(path, true) || [];
        if(!Array.isArray(path)) path = path.split('.');
        var data = trace[trace.length-2], d = this.data, p = path.slice(0);
        if(data) {
            d = data;
            p = p.slice(-1);
        }
        var previousValue = clone(getValue(d, p));
        setValue(d, p, value);
        // Lets trigger handlers now.
        for(var i = 0; i < path.length; i++) {
            // var type = Object.prototype.toString.call(trace[i], i);
            var where = path.slice(i + 1);
            var evData = {
                prev: previousValue,
                new:  value,
                where: where
            };
            if(options.eventData) evData.original = options.eventData;
            where = where.length === 0 ? undefined : where;
            triggerHandlers.call(this, path.slice(0, i + 1), evData);
        }
    };
    prototype.getValue = function(path, getTrace) {
        return getValue(this.data, path, getTrace);
    };
    prototype.push = function(path, value) {
        var that = this, pathRef = getValue(that.data, path), options = {
            eventData: {
                type: 'push'
            }
        };
        var eventData = options.eventData;
        if(pathRef && Array.isArray(pathRef)) {
            eventData.position = pathRef.length;
            that.setValue(path + '.' + pathRef.length, value, options);
        } else if(pathRef === undefined) {
            eventData.position = 0;
            that.setValue(path, [value], options);
        } else {
            return false;
        }
    };
    prototype.unshift = function(path, value) {
        var pathRef = getValue(this.data, path), setVal = this.setValue, eventData = {
            type: 'unshift',
            position: 0
        };
        if(pathRef && Array.isArray(pathRef)) {
            pathRef.unshift(value);
            this.setValue(path + '.0', value, eventData);
        } else if(pathRef === undefined) {
            this.setValue(path, [value], eventData);
        }
    };
    prototype.insert = function(path, pos, value) {
        var pathRef = getValue(this.data, path), setVal = this.setValue, eventData = {
            type: 'insert',
            position: pos
        };
        if(pathRef && Array.isArray(pathRef)) {
            pathRef.splice(pos, 0, value);
            this.setValue(path + '.' + pos, value, eventData);
        } else if(pathRef === undefined) {
            this.setValue(path, [value], eventData);
        }
    };
    prototype.remove = function(path, pos) {
        var pathRef = getValue(this.data, path), setVal = this.setValue, eventData = {
            type: 'delete',
            position: pos
        };
        if(Array.isArray(pathRef)) {
            if(pos > 0 && pos < pathRef.length) {
                pathRef.splice(pos, 1);
                triggerHandlers.call(this, path, eventData);
            }
        } else if(typeof pathRef === "object") {
            delete pathRef[pos];
            triggerHandlers.call(this, path, eventData);
        }
    };
    prototype.observe = function(path, handler) {
        this.handlers[path] = this.handlers[path] || [];
        this.handlers[path][this.handlers[path].length] = handler;
    };
    prototype.unobserve = function(path, handler) {
        var pathHandlers = this.handlers[path] || [];
        var idx = pathHandlers.indexOf(handler);
        if(idx > -1) {
            pathHandlers.splice(idx, 1);
        }
    };
    Observer.prototype = prototype;
    return Observer;
})();
\$\endgroup\$
7
  • 2
    \$\begingroup\$ Could you kindly show examples of how these functions are called \$\endgroup\$ Commented Aug 24, 2016 at 8:14
  • \$\begingroup\$ I think you miss the clone() function, and maybe something is not work properly, like the eventData.where. \$\endgroup\$ Commented Aug 24, 2016 at 8:37
  • \$\begingroup\$ @MarioAlexandroSantini Added clone method. \$\endgroup\$ Commented Aug 24, 2016 at 9:29
  • \$\begingroup\$ console.log() calls are quite expensive, and from by the looks of things you are calling it on every change so far. ( I get that it's for verifying functionality, however if you are having performance issues I would test it with some other action you can verify, and that doesn't have to call all the way out to debug interfaces) \$\endgroup\$ Commented Aug 24, 2016 at 11:40
  • \$\begingroup\$ @DJHenjin console.log is just for testing purpose. \$\endgroup\$ Commented Aug 24, 2016 at 12:00

0

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.