Is it possible to listen for changes to an object's attributes in JavaScript?

23,883

Solution 1

Thanks for the comments guys. I've gone with the following:

var EntriesRegistry = (function(){

    var instance = null;

    function __constructor() {

        var
            self = this,
            observations = {};

        this.set = function(n,v)
        {
            self[n] = v;

            if( observations[n] )
                for( var i=0; i < observations[n].length; i++ )
                    observations[n][i].apply(null, [v, n]);

        }

        this.get = function(n)
        {
            return self[n];
        }

        this.observe = function(n,f)
        {

            if(observations[n] == undefined)
                observations[n] = [];

            observations[n].push(f);
        }

    }

    return new function(){
        this.getInstance = function(){
            if (instance == null)
            {
                instance = new __constructor();
                instance.constructor = null;
            }
            return instance;
        }
    }
})();

var entries = EntriesRegistry.getInstance();

var test = function(v){ alert(v); };

entries.set('bob', 'meh');

entries.get('bob');

entries.observe('seth', test);

entries.set('seth', 'dave');

Taking on-board your comments, I'll be using event delegation on the form objects to update the registry and trigger the registered observing methods.

This is working well for me so far... can you guys see any problems with this?

Solution 2

As far as I know, there are no events fired on Object attribute changes (edit: except, apparently, for Object.watch).

Why not use event delegation wherever possible? That is, events on the form rather than on individual form elements, capturing events as they bubble up?

For instance (my jQuery is rusty, forgive me for using Prototype instead, but I'm sure you'll be able to adapt it easily):

$(form).observe('change', function(e) {
    // To identify changed field, in Proto use e.element()
    // but I think in jQuery it's e.target (as it should be)
});

You can also capture input and keyup and paste events if you want it to fire on text fields before they lose focus. My solution for this is usually:

  1. Gecko/Webkit-based browsers: observe input on the form.
  2. Also in Webkit-based browsers: observe keyup and paste events on textareas (they do not fire input on textareas for some reason).
  3. IE: observe keyup and paste on the form
  4. Observe change on the form (this fires on selects).
  5. For keyup and paste events, compare a field's current value against its default (what its value was when the page was loaded) by comparing a text field's value to its defaultValue

Edit: Here's example code I developed for preventing unmodified form submission and the like:

What is the best way to track changes in a form via javascript?

Solution 3

You could attach a listener to a container (the body or the form) and then use the event parameter to react to the change. You get all the listener goodness but only have to attach one for the container instead of one for every element.

$('body').change(function(event){
    /* do whatever you want with event.target here */
    console.debug(event.target); /* assuming firebug */
 });

The event.target holds the element that was clicked on.

SitePoint has a nice explanation here of event delegation:

JavaScript event delegation is a simple technique by which you add a single event handler to a parent element in order to avoid having to add event handlers to multiple child elements.

Solution 4

Mozilla-engined browsers support Object.watch, but I'm not aware of a cross-browser compatible equivalent.

Have you profiled the page with Firebug to get an idea of exactly what's causing the slowness, or is "lots of event handlers" a guess?

Solution 5

Small modification to the previous answer : by moving the observable code to an object, one can make an abstraction out of it and use it to extend other objects with jQuery's extend method.

ObservableProperties = {
    events : {},
    on : function(type, f)
    {
        if(!this.events[type]) this.events[type] = [];
        this.events[type].push({
            action: f,
            type: type,
            target: this
        });
    },
    trigger : function(type)
    {
        if (this.events[type]!==undefined)
        {
            for(var e = 0, imax = this.events[type].length ; e < imax ; ++e)
            {
                this.events[type][e].action(this.events[type][e]);
            }
        }
    },
    removeEventListener : function(type, f)
    {
        if(this.events[type])
        {
            for(var e = 0, imax = this.events[type].length ; e < imax ; ++e)
            {
                if(this.events[type][e].action == f)
                    this.events[type].splice(e, 1);
            }
        }
    }
};
Object.freeze(ObservableProperties);

var SomeBusinessObject = function (){
    self = $.extend(true,{},ObservableProperties);
    self.someAttr = 1000
    self.someMethod = function(){
        // some code
    }
    return self;
}

See the fiddle : https://jsfiddle.net/v2mcwpw7/3/

Share:
23,883

Related videos on Youtube

user3047801
Author by

user3047801

Delivery-focussed Elixir/Erlang, Python, XSL/XQuery, and (No)SQL developer, specialising in content/data migration, API/micro-service design, and Scrum mentorship.

Updated on July 09, 2022

Comments

  • user3047801
    user3047801 almost 2 years

    I'm working on a fiddly web interface which is mostly built with JavaScript. Its basically one (very) large form with many sections. Each section is built based on options from other parts of the form. Whenever those options change the new values are noted in a "registry" type object and the other sections re-populate accordingly.

    Having event listeners on the many form fields is starting to slow things down, and refreshing the whole form for each change would be too heavy/slow for the user.

    I'm wondering whether its possible to add listeners to the registry object's attributes rather than the form elements to speed things up a bit? And, if so, could you provide/point me to some sample code?

    Further information:

    • This is a plug-in for jQuery, so any functionality I can build-on from that library would be helpful but not essential.
    • Our users are using IE6/7, Safari and FF2/3, so if it is possible but only for "modern" browsers I'll have to find a different solution.
  • Sumi Straessle
    Sumi Straessle over 8 years
    One could argue that we can bind the ObservableProperties object directly to the business object by doing $.extend(true,this,ObservableProperties); It does look better. However, be careful about scope. I use a selfvariable when I do things a bit complicated with scope (or a Singleton for that matter), otherwise if everything is public, I go with this. See this fiddle
  • andersfylling
    andersfylling almost 8 years
    Object.observe() has been deprecated. Try to avoid using this as much as possible for your own sake.