Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Proxy object cannot be added to DOM (traps doesn't trigger either)

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).

like image 651
bjanes Avatar asked Aug 06 '16 08:08

bjanes


2 Answers

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.

like image 187
Jack Guy Avatar answered Oct 17 '22 07:10

Jack Guy


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

like image 37
Thomas Avatar answered Oct 17 '22 06:10

Thomas