Jake Freelander
Jake Freelander

Reputation: 1471

How to detect if an object variable has been changed?

I have a class that represents a fence, internally its made up from rectangle and circle marker objects (also my classes). The fence has 4 variables - x1, x2, y1 and y2. If any of these changes I have to modify or rebuild the internal marker objects.

Storing and checking the 4 values isn't such a big deal but this is just the first of my world object classes and there will be ones with much longer lists of variables. Is there any good way of checking whether any of these has changed or trigger something on change without explicitly storing double values and checking each time the canvas is redrawn? Something like a property in vb.net or such?

Upvotes: 7

Views: 4155

Answers (4)

David Sherret
David Sherret

Reputation: 106640

Using the object posted in the code below, you can achieve it quite easily:

function Fence() {
    // constructor
}

Fence.prototype.refresh = function() {
    // change the refresh code here
    console.log(this.x1 + "," + this.y1 + "," + this.x2 + "," + this.y2);
};

// must be called after the prototype.refresh function is defined
RefreshExtender.addRefreshProperties(Fence, [
        new RefreshExtender.Property("x1", 0), // propertyName, defaultValue[, refreshFunction]
        new RefreshExtender.Property("y1", 0, function() { console.log('Refresh only y1 property.'); }),
        new RefreshExtender.Property("x2", 0),
        new RefreshExtender.Property("y2", 0)
    ]);

Then when using it:

var fence = new Fence();
fence.x1 = 20;
// Outputs: "20,0,0,0"

Now if you change multiple properties at once, it will only call the refresh function after all the properties have been set. For example:

fence.x1 = 10; 
fence.x2 = 20;
// Outputs: "10,0,20,0 (Outputs only ONCE)"

If we update the y1 property, it will execute the function passed in when creating the property:

fence.y1 = 30;
// Outputs: "Refresh only y1 property."

Refresh Extender:

var RefreshExtender = {
    addRefreshProperties: function(baseType, properties) {
        function defineProperty(property) {
            Object.defineProperty(baseType.prototype, property.name, {
                get: function() {
                    var val = this["_" + property.name];
                    if (typeof val === "undefined") {
                        return property.defaultValue;
                    }
                    return val;
                },
                set: function(val) {
                    var shouldRefresh = this["_" + property.name] !== val;
                    this["_" + property.name] = val;
                    if (shouldRefresh) {
                        if (typeof property.refreshFunction === "function") {
                            property.refreshFunction();
                        }
                        else {
                            this.refresh();
                        }
                    }
                },
                enumerable: true,
                configurable: true
            });
        }

        for (var i = 0, l = properties.length; i < l; i++) {
            defineProperty(properties[i]);
        }

        var oldRefreshFunction = baseType.prototype.refresh;

        baseType.prototype.refresh = RefreshExtender._executeOnce(oldRefreshFunction);
    },
    Property : function(name, defaultValue, refreshFunction) {
        this.name            = name;
        this.defaultValue    = defaultValue;
        if (typeof refreshFunction === "function") {
            this.refreshFunction = RefreshExtender._executeOnce(refreshFunction);
        }
    },
    _executeOnce : function(originalFunc) {
        var isRefreshing = false,
            func = function() {
                var _this = this;
                if (!isRefreshing) {
                    isRefreshing = true;
                    setTimeout(function() {
                        isRefreshing = false;
                        originalFunc.call(_this);
                    }, 0);
                }
            };

        return func;
    }
};

Upvotes: 1

KJ Price
KJ Price

Reputation: 5964

var fence= {
   set x1(){
      alert('change');
      this.rebuild();
   },
   rebuild: function(){}
}

Also

function Fence(val){
    var value = val;

    this.__defineGetter__("x1", function(){
        return value;
    });

    this.__defineSetter__("x1", function(val){
        alert('change');
        this.rebuild();
    });
    this.rebuild = function(){};
}
var fence = new Fence();

Upvotes: 4

ckersch
ckersch

Reputation: 7687

A good approach may be to define a prototype for a fence rather than using a regular object. For a prototype, you can create setter methods rather than directly setting an attribute, and have the setter methods handle tracking changes to variables as well.

The prototype would look something like this:

function fence(x1, x2, x3, x4){
  this.x1 = x1;
  ....

  this.changedVars = {};
}

fence.Prototype.setX1 = function(newVal){
 this.x1 = newVal;
 this.changedVars.x1 = true;
};

This would collect all changed variables internally. When you ran a method (say, 'rebuildFence'), you would delete the keys you updated from this.changedVariables. Keeping all of the setter methods (and getter methods, if you are so inclined), on the prototype will also reduce memory overhead, since you won't be redefining the functions every time you build a fence.

Upvotes: 0

dave
dave

Reputation: 64657

You could create a closure with access to setter and getter methods, but no direct access to the properties. Something like:

var fence = function() {
    var x1, x2, y1, y2; 
    var setX1 = function(x) {
            if (typeof x == 'undefined') return x1;
            if (x != x1) alert('It Changed');
            x1 = x; 
    };
    var setX2 = function(x) {
            if (typeof x == 'undefined') return x2;
            if (x != x2) alert('It Changed');
            x2 = x; 
    };
    var setY1 = function(y) {
            if (typeof x == 'undefined') return y1;
            if (y != y1) alert('It Changed');
            y1 = y; 
    };
    var setY2 = function(y) { 
            if (typeof y == 'undefined') return y1;
            if (y != y2) alert('It Changed');
            y2 = y; 
    };
    return { x1: setX1, 
             x2: setX2, 
             y1: setY1, 
             y2: setY2 
           }
}()

fence.x1(1); //alerts "It changed"
fence.x1(1); //no alert

Upvotes: 0

Related Questions