Skip to content

Instantly share code, notes, and snippets.

@MicroCBer
Created August 17, 2023 08:22
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 MicroCBer/a412a6c27a6dc0e49c091ce64e96ae6b to your computer and use it in GitHub Desktop.
Save MicroCBer/a412a6c27a6dc0e49c091ce64e96ae6b to your computer and use it in GitHub Desktop.
Create an element delegate/proxy in js
const createElementDelegate = (ele, config) => {
const old2NewMap = new WeakMap(), new2OldMap = new WeakMap();
const clone = (ele, {cloneParent, syncProps, delegateEvents=true, delayedElementListChange}={}) => {
if(ele == null) return ele;
// clone, and delegate events, and preserve parent classes
let cloned = ele.cloneNode();
old2NewMap.set(ele, cloned);
new2OldMap.set(cloned, ele);
// clone child also
for(const child of ele.childNodes)
cloned.appendChild(clone(child));
// event delegate
if(delegateEvents)
for(const prop in ele){
if(prop.startsWith('on')){
cloned[prop] = e => ele.dispatchEvent(new e.constructor(e.type,e))
}
}
if(cloneParent) {
// wrap in fake parents
let parent = ele;
while(parent=parent.parentElement){
if(parent.tagName==='HTML') break;
const clonedParent = parent.cloneNode();
clonedParent.appendChild(cloned);
cloned = clonedParent;
}
}
if(syncProps) {
new MutationObserver((records)=>{
for(const record of records) {
const clonedTarget = old2NewMap.get(record.target);
const target = record.target;
if(record.type === 'attributes') {
const attr = record.attributeName;
clonedTarget.setAttribute(attr,target.getAttribute(attr));
}
else if(record.type === 'childList') {
const process = ()=>{
for(const added of record.addedNodes) {
if(old2NewMap.get(record.nextSibling))
clonedTarget.insertBefore(clone(added), old2NewMap.get(record.nextSibling));
else
clonedTarget.appendChild(clone(added));
}
for(const removed of record.removedNodes) {
const clonedRemoved = old2NewMap.get(removed);
if(clonedRemoved) {
clonedRemoved.remove();
}
}
};
if(delayedElementListChange) setTimeout(process);
else process();
}
}
}).observe(ele, {attributes: true, childList: true, subtree: true})
}
return cloned;
}
return clone(ele, config);
}
// run in any stackoverflow question pages with answers.
// this would create a perfect delegate dom of answers element, all the buttons should be interactable.
// sadly, keyboard input is not working
const win = window.open('about:blank', undefined, 'popup');
for(const style of document.querySelectorAll('style, link'))
win.document.head.appendChild(style.cloneNode(true));
win.document.body.appendChild(createElementDelegate(document.querySelector("#answers"), {cloneParent:true, syncProps: true}))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment