public
Last active

object.watch polyfill in ES5

  • Download Gist
object-watch.js
JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
/*
* object.watch polyfill
*
* 2012-04-03
*
* By Eli Grey, http://eligrey.com
* Public Domain.
* NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
*/
 
// object.watch
if (!Object.prototype.watch) {
Object.defineProperty(Object.prototype, "watch", {
enumerable: false
, configurable: true
, writable: false
, value: function (prop, handler) {
var
oldval = this[prop]
, newval = oldval
, getter = function () {
return newval;
}
, setter = function (val) {
oldval = newval;
return newval = handler.call(this, prop, oldval, val);
}
;
if (delete this[prop]) { // can't watch constants
Object.defineProperty(this, prop, {
get: getter
, set: setter
, enumerable: true
, configurable: true
});
}
}
});
}
 
// object.unwatch
if (!Object.prototype.unwatch) {
Object.defineProperty(Object.prototype, "unwatch", {
enumerable: false
, configurable: true
, writable: false
, value: function (prop) {
var val = this[prop];
delete this[prop]; // remove accessors
this[prop] = val;
}
});
}

Thanks for this snippet! It's way beyond my level of JS and incredibly useful.

I did have to modify it somewhat - the setter function is broken if the handler function doesn't return the new value. Took me a while, but I figured out how to fix it!

Tobriand, are you able to contribute the fix that you said you made, to the author for review and possible inclusion in the script. That could be helpful to others.

Thanks for building this. What license is it released under? (no license === All Rights Reserved...at least in the US). Thanks.

Cool. Would you mind adding the MIT header? http://en.wikipedia.org/wiki/MIT_License#License_terms Sadly, that's the only way it really "counts." Thanks.

I'd have a hard time in court proving that I didn't put this under the MIT license after the comment I just explicitly made.

I completely understand and agree. :) However, "corporate types" generally steer clear of any code that doesn't include both a copyright and a licensing claim (even if it's just "@license MIT" or some such). I'll likely use this in my own projects, regardless. :) Thanks for the license change. Making it more explicit may get it more usage, but that's completely your call.

In any case, I appreciate you licensing it and discussing this with me. Thanks, Eli.

There is a conflict with this script and Mootools that I can't seem to work out. I'm using MooTools Core 1.4.1 with MooTools More 1.4.01 (Specifically with Form.Request).

The error only happens in browsers that don't have native Object.watch(). Specifically, the error is:

Uncaught TypeError Object (... the watch() function above ...) has no method include.

Here's an example of the code that kicks an error:

window.addEvent('domready', function(){
  document.id('TestForm').formUpdate(null,{
    onComplete: function(target, tree, elements, html, javascript){
      console.log('Done!!')
    }
  });
});

The conflicting code is in MooTools Core, on the this.Events.addEvent() function; excerpt below:

 this.$events[type] = (this.$events[type] || []).include(fn);

I'm guessing that the 'watch' function you've created is conflicting with a MooTools internal "watch" function, event, or other reserved word.

Update
Seems that the culprit is a for in loop. Oddly enough, the object passed to that loop only has three members. However, the for loops through 5 members, the additional two being watch and unwatch. Example:

var events = {event1: function(),event2: function(), event3: function()};

console.log(events);

for (var fn in events) console.log(fn);

Modifying the Object.prototype screws with for in loops. In the case of mootools, I'll be using Object.extend('watch',function(){}); instead of adjusting the prototype.

Also, for the time being, I've only found that this conflicts with Events.Pseudos of MooTools More. I've added the following code as a hack to get it functioning:

Object.prototype.watch.extend('include',function(){return});

I've forked this for those that might want to use this with MooTools: https://gist.github.com/1319481

thank you so much, Ive made a fork that could be useful:
https://gist.github.com/1627705

take a look at the usage:

object.watchAll(function(){
alert("some attribute of object has been changed");
});

object.watchMany([
"attr1", "attr2"
],
function(){
alert("attribute attr1 or attr2 of object has been changed");
});

object.watchOne("attr1", function(){
alert("attribute attr1 of object has been changed");
});

object.attr1 = "different value"; //will trigger a object.attr1 watch or object watchAll
//WORKS WITH ARRAYS TOO!
object.attr1.push("new item"); //will trigger a object.attr1 watch or object watchAll

I keep getting a "Uncaught TypeError: Cannot call method 'call' of undefined" on the handler on line 12 in the code above.
Is this a know problem? Is there a way to work around it? I am on Chrome.

You need to specify a handler function. I'm guessing that you're only calling object.watch(property) or the function you passed for the handler was null.

I just raised the minimum requirements by only supporting Object.defineProperty, as __define[GS]etter__-only browsers are now an ancient minority. @ericcholis: object.watch and object.unwatch are also unenumerable now so that bug won't happen anymore.

No, the error was on page load. The new code does not throw the error, i just have to make sure everything else works.
Thanks for the quick update!

The update had nothing to do with your issue, so you were probably calling object.watch too soon before a function was defined or something and later fixed it.

can you show some sample code on how to use this? thank you

WTF with mixing comma first AND comma last in the same code?

@rwldrn Sorry, when I originally wrote this it was in comma-last form, and I forgot to completely update the code style in the recent update. I just fixed it.

Is IE8 still supported? When I try out a working page (in Safari, Chrome, ...) I get the following error:
"Message: Object doesn't support this property or method" on line 13.

Yes, but only for DOM objects.

This seems to be exactly what I was looking for, but will it work in all popular browsers/versions? My JS book doesn't even mention defineProperty.

It works with all current versions of all browsers. If you want to use it in IE8, it will only work on DOM objects.

Thank you. Apart from your solution, I haven't been able to find a way to tell if an object attribute changes, so I trust there is no solution that will work in browser versions in common use?

@eligrey a time a go I was working on a fork of your object-watch, great job by the way, and got the same problem (obvious), today I am working on another library that handle this problem. Not with the same possibilities but it is nice too. Note that I even tested it yet, maybe tomorrow it will be finished :)
You can find it here:
https://gist.github.com/2956493

Is there anything out there to watch all changes to properties of an object?

This plus debugger; in Chrome = tasty.

ItsLeeOwen, I might be two months late, but maybe you should run a for loop to add watch to each property object. That seems the pretty quick and simple.

Thanks for this one!

Would calling unwatch from within the watch function for any reason potentially cause problems?

Hi there,
many thanks for this polyfill, but I've found a problem with it. It won't work for watching accessor properties (the ones with a get/set), while the original Mozilla's function does.
So, I've rolled my own version here:
https://gist.github.com/XoseLluis/4750176

Awesome thank you!

What if the property already has a getter and setter. Doesn't this override them?

Thanks so much, this snippet saved me! :)

Seems like a pretty elegant and optimal solution...

Nice job !

I tried the following example from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/watch :

var o = { p: 1 };

o.watch("p", function (id, oldval, newval) {
    console.log( "o." + id + " changed from " + oldval + " to " + newval );
    return newval;
});

o.p = 2;
o.p = 3;
delete o.p;
o.p = 4;

o.unwatch('p');
o.p = 5;

The code breaks after the 'delete' statement. Firefox (using its native implementation) still triggers the 'watch' statement while Chrome (using the polyfill) doesn't. It's a minor issue, but I figured it's still worth mentioning...

When I remove the 'delete' statement, both both browsers are in synch.

Hi!

Thanks, this is great - I created my own version after seeing yours here:
https://github.com/dtpz/DataWatch/blob/master/DataWatch.js

It takes what you did to the next level by allowing mutation of the set value (though this must be explicit), taking it off of the object prototype, and allowing multiple watchers and possibility to unwatch a specific function (the same way it's done with events).

Please tell me what you think - and thanks!

Very usefull piece of code, you saved my day. Thanks.

Someone up above mentioned that the setter function does not work properly:

a = {b:"c", d:"e"}
Object {b: "c", d: "e"}

a.watch("b", function() { console.log("change, new a: ", a) } ) 
undefined

a.b = "new value of B"
change, new a:  Object {d: "e", b: undefined}
"new value of B"

a
Object {d: "e", b: undefined}

Social proof - https://github.com/melanke/Watch.JS/ - 1000 stars :)

Awesome, thank you!

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.