Skip to content

Instantly share code, notes, and snippets.

@dfkaye
Created November 29, 2023 23:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dfkaye/dae6958adf813735d9154be23a0ebb09 to your computer and use it in GitHub Desktop.
Save dfkaye/dae6958adf813735d9154be23a0ebb09 to your computer and use it in GitHub Desktop.
onpropertychange signal v.5.js - propertychange events on a node with a mixture of property descriptors and proxy handlers
// 20 September 2023
// onpropertychange signal v.5
// cont'd from https://gist.github.com/dfkaye/428abdeca224c37014d27fc420906d88
// propertychange events on a node with a mixture of property descriptors and
// proxy handlers
// come back to this for the onpropertychange part
// SEE sketch 17 September 2023,
// "onpropertychange for form element nodes"...
/************************************************************/
// 21 September 2023
// Back and Forth
// "works" on elements again but not for non-EventTargets....
/************************************************************/
function N(node) {
var n = Object(node);
// facade over the node's properties.
// this will be proxied to enable attribute and event calls on the proxy,
// and delegate calls on methods or accessing property values on the node.
var o = {};
// have to use IIFE closures to scope each name, otherwise we get the
// classic "last value of name" problem in each property accessor.
for (var name in n) {
if (typeof n[name] == "function") {
~((f) => {
Object.defineProperty(o, name, {
value: function () {
// console.log(n, ...arguments);
return f.apply(n, Array.from(arguments));
}
});
})(n[name]);
} else {
~((name) => {
Object.defineProperty(o, name, {
get() { return Reflect.get(n, name); },
set(value) {
console.log(
"%cpropertychange",
"font-weight: bold;",
n, name, value
);
return Reflect.set(n, name, value);
}
});
})(name);
}
}
var API = {
onpropertychange: null,
toJSON() { return n; },
toString() { return `[object ${n.constructor.name}]`}
};
Object.assign(o, API);
var handler = {
defineProperty(target, key, descriptor) {
if (!(key in o)) {
~((name) => {
console.warn(`%cdefining ${name}`, "font-weight: bold;");
Object.defineProperty(o, name, {
// Very interesting: Have to set this true or proxy throws
// TypeError: proxy can't define an incompatible property descriptor
// ('"fake"', proxy can't report an existing non-configurable
// property as configurable)
configurable: true,
get() { return Reflect.get(n, name); },
set(value) {
console.log(
"%cproxy-added propertychange",
"font-weight: bold;",
n, name, value
);
return Reflect.set(n, name, value);
}
});
})(key);
}
// copy+pasta from onpropertychange for form element nodes 17 September
// target is o, n is node...
var previous = Reflect.get(target, key);
var value = descriptor.value;
if (previous === value) {
return;
}
if (key == 'onpropertychange') {
var h = Reflect.get(n, key);
target.removeEventListener('propertychange', h);
target.addEventListener('propertychange', value);
return Reflect.set(n, key, value);
}
Reflect.set(target, key, value);
return target.dispatchEvent(new CustomEvent("propertychange", {
detail: { propertyName: key, previous, value }
}));
},
deleteProperty(target, key) {
console.warn(`%cdeleting ${key}`, "font-weight: bold;");
if (key == 'onpropertychange') {
var value = Reflect.get(n, key);
Reflect.deleteProperty(n, key);
return target.removeEventListener('propertychange', value);
}
return Reflect.deleteProperty(target, key);
}
};
return new Proxy(o, handler);
// return o;
}
var html = `
<input name="test" value="15">
<div name="div">div test</div>
`;
var body = (new DOMParser).parseFromString(html, "text/html").body;
var input = body.querySelector("input");
var div = body.querySelector("div");
function test(node) {
console.group(node.nodeName);
var n = N(node);
n.value = 'update';
n.setAttribute('value', 'updated again');
n.addEventListener("propertychange", function h (e) {
console.log("%cpropertychange listener", "font-weight: bold;")
console.warn(e);
});
n.onpropertychange = function (e) {
console.log("%conpropertychange handler", "font-weight: bold;");
console.warn(e);
};
n.value = "graphic";
n.setAttribute('value', 'reducted');
n.fake = '12321313';
console.log( n.toString() );
console.log( JSON.stringify(n) )
console.log( n.toJSON() );
console.log( n.outerHTML );
n.onpropertychange = null;
delete n.onpropertychange;
delete n.fake;
n.fake = "restored fake";
console.groupEnd(node.nodeName);
}
test(input);
test(div);
test(Object.setPrototypeOf({}, {
nodeName: "FAKEr",
setAttribute() {},
addEventListener() {},
dispatchEvent() {},
removeEventListener() {}
}));
// log output to integrate with console.assert() calls....
/*
INPUT
propertychange
<input name="test" value="15">
value update
propertychange
<input name="test" value="updated again">
value graphic
defining fake
proxy-added propertychange
<input name="test" value="reducted">
fake 12321313
propertychange listener
propertychange {
target: input, isTrusted: false, detail: {…}, srcElement: input,
currentTarget: input, eventPhase: 2, bubbles: false, cancelable: false,
returnValue: true, defaultPrevented: false, … }
onpropertychange handler
propertychange {
target: input, isTrusted: false, detail: {…}, srcElement: input,
currentTarget: input, eventPhase: 2, bubbles: false, cancelable: false,
returnValue: true, defaultPrevented: false, … }
[object HTMLInputElement]
{"fake":"12321313"}
<input name="test" value="reducted">
<input name="test" value="reducted">
deleting onpropertychange
deleting fake
defining fake
proxy-added propertychange
<input name="test" value="reducted">
fake restored fake
propertychange listener
propertychange {
target: input, isTrusted: false, detail: {…}, srcElement: input,
currentTarget: input, eventPhase: 2, bubbles: false, cancelable: false,
returnValue: true, defaultPrevented: false, … }
DIV
defining value
proxy-added propertychange
<div name="div">
value update
proxy-added propertychange
<div name="div" value="updated again">
value graphic
defining fake
proxy-added propertychange
<div name="div" value="reducted">
fake 12321313
propertychange listener
propertychange {
target: div, isTrusted: false, detail: {…}, srcElement: div,
currentTarget: div, eventPhase: 2, bubbles: false, cancelable: false,
returnValue: true, defaultPrevented: false, … }
onpropertychange handler
propertychange {
target: div, isTrusted: false, detail: {…}, srcElement: div,
currentTarget: div, eventPhase: 2, bubbles: false, cancelable: false,
returnValue: true, defaultPrevented: false, … }
[object HTMLDivElement]
{"value":"graphic","fake":"12321313"}
<div name="div" value="reducted">
<div name="div" value="reducted">div test</div>
deleting onpropertychange
deleting fake
defining fake
proxy-added propertychange
<div name="div" value="reducted">
fake restored fake
propertychange listener
propertychange {
target: div, isTrusted: false, detail: {…}, srcElement: div,
currentTarget: div, eventPhase: 2, bubbles: false, cancelable: false,
returnValue: true, defaultPrevented: false, … }
FAKEr
defining value
proxy-added propertychange
Object { }
value update
proxy-added propertychange
Object { value: "update", onpropertychange: onpropertychange(e) }
value graphic
defining fake
proxy-added propertychange
Object { value: "graphic", onpropertychange: onpropertychange(e) }
fake 12321313
[object Object]
{"value":"graphic","fake":"12321313"}
Object {
value: "graphic",
onpropertychange: onpropertychange(e),
fake: "12321313" }
undefined
deleting onpropertychange
deleting fake
defining fake
proxy-added propertychange
Object { value: "graphic", fake: "12321313" }
fake restored fake
undefined
*/
// However, the implementation doesn't work for non-EventTargets....
var o = N({});
o.onpropertychange = function (e) { console.warn(e); };
o.name = "added";
// Uncaught TypeError: target.removeEventListener is not a function
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment