Home Listening for variable changes in JavaScript
Reply: 13

Listening for variable changes in JavaScript

rashcroft22
1#
rashcroft22 Published in 2009-11-18 23:58:15Z

Is it possible to have an event in JS that fires when the value of a certain variable changes? JQuery is accepted. Thanks!

Eli Grey
2#
Eli Grey Reply to 2012-04-21 18:43:31Z

Yes, object.watch (it's non-standard though). Here's my implementation that works in every current major browser.

Luke Schafer
3#
Luke Schafer Reply to 2009-11-19 00:33:25Z

No.

But, if it's really that important, you have 2 options (first is tested, second isn't):

First, use setters and getters, like so:

var myobj = {a : 1};

function create_gets_sets(obj) { // make this a framework/global function
    var proxy = {}
    for ( var i in obj ) {
        if (obj.hasOwnProperty(i)) {
            var k = i;
            proxy["set_"+i] = function (val) { this[k] = val; };
            proxy["get_"+i] = function ()    { return this[k]; };
        }
    }
    for (var i in proxy) {
        if (proxy.hasOwnProperty(i)) {
            obj[i] = proxy[i];
        }
    }
}

create_gets_sets(myobj);

then you can do something like:

function listen_to(obj, prop, handler) {
    var current_setter = obj["set_" + prop];
    var old_val = obj["get_" + prop]();
    obj["set_" + prop] = function(val) { current_setter.apply(obj, [old_val, val]); handler(val));
}

then set the listener like:

listen_to(myobj, "a", function(oldval, newval) {
    alert("old : " + oldval + " new : " + newval);
}

Second, I actually forgot, I'll submit while I think about it :)

EDIT: Oh, I remember :) You could put a watch on the value:

Given myobj above, with 'a' on it:

function watch(obj, prop, handler) { // make this a framework/global function
    var currval = obj[prop];
    function callback() {
        if (obj[prop] != currval) {
            var temp = currval;
            currval = obj[prop];
            handler(temp, currval);
        }
    }
    return callback;
}

var myhandler = function (oldval, newval) {
    //do something
};

var intervalH = setInterval(watch(myobj, "a", myhandler), 100);

myobj.set_a(2);
Akira
4#
Akira Reply to 2016-05-24 01:46:20Z

Yes, this is now completely possible!

I know this is an old thread but now this effect is possible using accessors (getters and setters): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects#Defining_getters_and_setters

You can define an object like this, in which aInternal represents the field a:

x = {
  aInternal: 10,
  aListener: function(val) {},
  set a(val) {
    this.aInternal = val;
    this.aListener(val);
  },
  get a() {
    return this.aInternal;
  },
  registerListener: function(listener) {
    this.aListener = listener;
  }
}

Then you can register a listener using the following:

x.registerListener(function(val) {
  alert("Someone changed the value of x.a to " + val);
});

So whenever anything changes the value of x.a, the listener function will be fired. Running the following line will bring the alert popup:

x.a = 42;

See an example here: https://jsfiddle.net/5o1wf1bn/1/

You can also user an array of listeners instead of a single listener slot, but I wanted to give you the simplest possible example.

andrew
5#
andrew Reply to 2016-03-28 18:08:46Z

Sorry to bring up an old thread, but here is a little manual for those who (like me!) don't see how Eli Grey's example works:

var test = new Object();
test.watch("elem", function(prop,oldval,newval){
    //Your code
    return newval;
});

Hope this can help someone

Eduardo Cuomo
6#
Eduardo Cuomo Reply to 2016-11-04 13:00:27Z

Using Prototype: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

// Console
function print(t) {
  var c = document.getElementById('console');
  c.innerHTML = c.innerHTML + '<br />' + t;
}

// Demo
var myVar = 123;

Object.defineProperty(this, 'varWatch', {
  get: function () { return myVar; },
  set: function (v) {
    myVar = v;
    print('Value changed! New value: ' + v);
  }
});

print(varWatch);
varWatch = 456;
print(varWatch);
<pre id="console">
</pre>

Other example

// Console
function print(t) {
  var c = document.getElementById('console');
  c.innerHTML = c.innerHTML + '<br />' + t;
}

// Demo
var varw = (function (context) {
  return function (varName, varValue) {
    var value = varValue;
  
    Object.defineProperty(context, varName, {
      get: function () { return value; },
      set: function (v) {
        value = v;
        print('Value changed! New value: ' + value);
      }
    });
  };
})(window);

varw('varWatch'); // Declare
print(varWatch);
varWatch = 456;
print(varWatch);

print('---');

varw('otherVarWatch', 123); // Declare with initial value
print(otherVarWatch);
otherVarWatch = 789;
print(otherVarWatch);
<pre id="console">
</pre>

Community
7#
Community Reply to 2017-05-23 11:47:30Z

As Luke Schafer's answer (note: this refers to his original post; but the whole point here remains valid after the edit), I would also suggest a pair of Get/Set methods to access your value.

However I would suggest some modifications (and that's why I'm posting...).

A problem with that code is that the field a of the object myobj is directly accessible, so it's possible to access it / change its value without triggering the listeners:

var myobj = { a : 5, get_a : function() { return this.a;}, set_a : function(val) { this.a = val; }}
/* add listeners ... */
myobj.a = 10; // no listeners called!

Encapsulation

So, to guarantee that the listeners are actually called, we would have to prohibit that direct access to the field a. How to do so? Use a closure!

var myobj = (function() { // Anonymous function to create scope.

    var a = 5;            // 'a' is local to this function
                          // and cannot be directly accessed from outside
                          // this anonymous function's scope

    return {
        get_a : function() { return a; },   // These functions are closures:
        set_a : function(val) { a = val; }  // they keep reference to
                                            // something ('a') that was on scope
                                            // where they were defined
    };
})();

Now you can use the same method to create and add the listeners as Luke proposed, but you can rest assured that there's no possible way to read from or write to a going unnoticed!

Adding encapsulated fields programmatically

Still on Luke's track, I propose now a simple way to add encapsulated fields and the respective getters/setters to objects by the means of a simple function call.

Note that this will only work properly with value types. For this to work with reference types, some kind of deep copy would have to be implemented (see this one, for instance).

function addProperty(obj, name, initial) {
    var field = initial;
    obj["get_" + name] = function() { return field; }
    obj["set_" + name] = function(val) { field = val; }
}

This works the same as before: we create a local variable on a function, and then we create a closure.

How to use it? Simple:

var myobj = {};
addProperty(myobj, "total", 0);
window.alert(myobj.get_total() == 0);
myobj.set_total(10);
window.alert(myobj.get_total() == 10);
ses
8#
ses Reply to 2013-12-12 14:38:53Z

AngularJS (I know this is not JQuery, but that might help. [Pure JS is good in theory only]):

$scope.$watch('data', function(newValue) { ..

where "data" is name of your variable in the scope.

There is a link to doc.

Chuck Han
9#
Chuck Han Reply to 2011-05-25 00:37:44Z

If you're using jQuery {UI} (which everyone should be using :-) ), you can use .change() with a hidden <input/> element.

MandoMando
10#
MandoMando Reply to 2012-06-06 15:05:29Z

For those tuning in a couple years later:

A solution for most browsers (and IE6+) is available that uses the onpropertychange event and the newer spec defineProperty. The slight catch is that you'll need to make your variable a dom object.

Full details:

http://johndyer.name/native-browser-get-set-properties-in-javascript/

jldupont
11#
jldupont Reply to 2009-11-19 00:06:42Z

Not directly: you need a pair getter/setter with an "addListener/removeListener" interface of some sort... or an NPAPI plugin (but that's another story altogether).

jarederaj
12#
jarederaj Reply to 2014-03-08 16:45:11Z

The functionality you're looking for can be achieved through the use of the "defineProperty()" method--which is only available to modern browsers:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

I've written a jQuery extension that has some similar functionality if you need more cross browser support:

https://github.com/jarederaj/jQueue

A small jQuery extension that handles queuing callbacks to the existence of a variable, object, or key. You can assign any number of callbacks to any number of data points that might be affected by processes running in the background. jQueue listens and waits for these data you specify to come into existence and then fires off the correct callback with its arguments.

markvgti
13#
markvgti Reply to 2015-02-11 05:50:09Z
//ex:
/*
var x1 = {currentStatus:undefined};
your need is x1.currentStatus value is change trigger event ?
below the code is use try it.
*/
function statusChange(){
    console.log("x1.currentStatus_value_is_changed"+x1.eventCurrentStatus);
};

var x1 = {
    eventCurrentStatus:undefined,
    get currentStatus(){
        return this.eventCurrentStatus;
    },
    set currentStatus(val){
        this.eventCurrentStatus=val;
      //your function();
    }
};

or

/*  var x1 = {
eventCurrentStatus:undefined,
currentStatus : {
    get : function(){
        return Events.eventCurrentStatus
        },
    set : function(status){
        Events.eventCurrentStatus=status;

    },
}*/
console.log("eventCurrentStatus = "+ x1.eventCurrentStatus);
x1.currentStatus="create"
console.log("eventCurrentStatus = "+ x1.eventCurrentStatus);
x1.currentStatus="edit"
console.log("eventCurrentStatus = "+ x1.eventCurrentStatus);
console.log("currentStatus = "+ x1.currentStatus);

or

/* global variable ku*/
    var jsVarEvents={};
    Object.defineProperty(window, "globalvar1", {//no i18n
        get: function() { return window.jsVarEvents.globalvarTemp},
        set: function(value) { window.window.jsVarEvents.globalvarTemp = value; }
    });
    console.log(globalvar1);
    globalvar1=1;
    console.log(globalvar1);
Neil Strain
14#
Neil Strain Reply to 2016-02-25 00:38:59Z

Yet again, I am fashionably late to this party. I know this is not pure JavaScript or jQuery; however, I've have been toying with a project in knockout.js, and have found the subscribe() method works great for firing an event (or notifying) when the value of something changes. Here is a simple example:

var foo = ko.observable(false);

foo.subscribe(function () {
    console.log('`foo` has changed: ' + foo());
    if (foo() === true) {
        // do something here
    }
});

Granted, subscribe() works on knockout's "observables" not on regular variables. Here is a link to the knockout documentation page covering observables and subscriptions, for anyone interested: http://knockoutjs.com/documentation/observables.html

You need to login account before you can post.

About| Privacy statement| Terms of Service| Advertising| Contact us| Help| Sitemap|
Processed in 0.603741 second(s) , Gzip On .

© 2016 Powered by mzan.com design MATCHINFO