8

I am trying to make a Proxy object of Image to trap properties but even with an empty handler I get an error message.

TypeError: Argument 1 of Node.appendChild does not implement interface Node.

The proxy object is suppose to act as the target object so this baffles me a little. As far as I understand you should be able to do this with DOM nodes as well (?).

Also: I cannot start loading the image and have the onload handler triggered when setting the src property.

How should I use the Proxy so I can "take over" for example the "src" property and otherwise have it act like a regular image object?

My code

'use strict';

//--- normal image use ---
var imgNormal = new Image();
imgNormal.onload = function(){
  console.log('Normal loaded OK');
  document.body.appendChild(imgNormal);
};
imgNormal.src = 'https://i.imgur.com/zn7O7QWb.jpg';

//--- proxy image ---
var imgProxy = new Proxy(Image, { // I also tried with 'new Image()' and HTMLImageElement
  set: function(a,b,c,d){
    console.log('set '+b);
    return Reflect.set(a,b,c,d);
  }
});
imgProxy.onload = function(){
  console.log('Proxy loaded OK');
  document.body.appendChild(imgProxy);
};
imgProxy.src = 'https://i.imgur.com/zn7O7QWb.jpg';

document.body.appendChild(imgProxy); // double-up to demo error

Update: Thanks to @Harangue! using "new" (bah..) certainly made the proxy object come to life but now I am unable to trap the setting of properties. It seem to ignore the trap completely - example:

var proxy = new Proxy(Image, {
      set: function(a,b,c,d){
        console.log('set '+b);        // doesn't show
        return Reflect.set(a,b,c,d);
      }
    });

    var imgProxy = new proxy();
    imgProxy.onload = function(){
      console.log('Proxy loaded OK');
      document.body.appendChild(imgProxy);
    };
    imgProxy.src = 'https://i.imgur.com/zn7O7QWb.jpg';

How can I trap the property setting using a valid proxy?

Update 2 On the other hand - using new with the new proxy only seem to use the original constructor. All examples I can find does not use new:

var myProxy = new Proxy(.., ..);  // should suffer

Using then on top of that new myProxy() only seem to use the original constructor which is not what I want as it ignores the traps.

var proxy = new Proxy(Image, {}); //should be sufficent??
var proxy2 = new proxy();
console.log(proxy2); //-> says Image (not proxy..)

The traps seem to work in my first attempts but the proxy doesn't behave as expected. This is so confusing, and so new. Happy for any input how both of these can be solved (traps and behavior).

bjanes
  • 263
  • 3
  • 11

2 Answers2

5

Never underestimate the importance of the new keyword. ;)

//--- proxy image ---
var imgProxy = new Proxy(Image, {  // I also tried with 'new Image()'
  set: function(a,b,c,d){
    console.log('set '+b);
    return Reflect.set(a,b,c,d);
  }
});
imgProxy.src = 'https://i.imgur.com/zn7O7QWb.jpg';

document.body.appendChild(new imgProxy); // double-up to demo error

With the proxy you effectively extend the Image object. But sending the Image constructor itself, rather than the DOM Node returned by it, would indeed be missing the needed appendChild.

Jack Guy
  • 8,346
  • 8
  • 55
  • 86
  • Ah yeah, I did indeed forget the new keyword. BUT, there is a new problem now as I cannot trap the setting. Have I overlooked another thing here? i updated answer with a new example for that case. Thanks! – bjanes Aug 06 '16 at 10:20
  • OTOH.. after studying the examples it doesn't appear that this is the common way to use a proxy. I mean, it seem that new Proxy(...) should be enough. Are you sure about the need for "new"? It seem to just use the Image's constructor without passing through the proxy. but I could misunderstand. :/ – bjanes Aug 06 '16 at 11:50
  • @bjanes When I use that code I get a console message whenever `src` is set. Are you looking for different behavior? – Jack Guy Aug 06 '16 at 18:10
  • OK, I think I understand... I basically have set up the proxy as I want *before* I instantiate it. Got it! Thanks, I will play around with this some more. – bjanes Aug 06 '16 at 21:35
  • It seems to me like this wouldn't trap properties set internally. – Michael May 22 '22 at 00:26
4

As an alternative to a proxy you can also overwrite the property on the object itself and therefore control it's behaviour:

function findDescriptor(obj, prop){
    if(obj != null){
        return Object.hasOwnProperty.call(obj, prop)?
            Object.getOwnPropertyDescriptor(obj, prop):
            findDescriptor(Object.getPrototypeOf(obj), prop);
    }
}

var img = new Image();
var {get, set} = findDescriptor(img, "src");

Object.defineProperty(img, "src", {
    configurable: true,
    enumerable: true,

    //get: get,  //keep behaviour
    get(){       //overwrite getter
        var v = get.call(this);  //call the original getter
        console.log("get src:", v, this);
        return v;
    },

    //same for setter
    set(v){
        console.log("set src:", v, this);
        //modify value before applying it to the default setter
        v = v.toLowerCase();
        set.call(this, v);
    }
});

img.src = "FileWithUppercaseLetters.jpg"; //setter
img.src;  //trigger getter

And since this property is defined on the Image.prototype*, you can simply extend this class and modify the behaviour on the prototype of the inherited Class

*at least in FF, have to check the other browsers

Thomas
  • 11,958
  • 1
  • 14
  • 23
  • Thanks! This is definitely an alternative I will have in mind. I was unable to override properties which is why I looked at proxies. Didn't know you could do it this way. nice! – bjanes Aug 06 '16 at 10:21