Created
November 29, 2023 23:20
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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