Skip to content

Instantly share code, notes, and snippets.

@dfkaye
Last active November 29, 2023 23:50
Show Gist options
  • Save dfkaye/619c5f31080fce2cd383ac966e132311 to your computer and use it in GitHub Desktop.
Save dfkaye/619c5f31080fce2cd383ac966e132311 to your computer and use it in GitHub Desktop.
onpropertychange signal v.1 - using object descriptors
// 15 September 2023
// onpropertychange signal v.1
// This begins a 12-day odyssey in search of a successful implementation of the
// `onpropertychange` handler once supported by Internet Explorer in browser
// days of yore.
// cf. v.9 for the successful solution that solves both Object and EvenTarget,
// https://gist.github.com/dfkaye/2c7de4ba9bf7758f3c052378ce46219a
// Define Object.prototype.onpropertychange
// Very limited implementation because we have to register each property name,
// individually. Also the prototype methods, such as array.push() and what-have-
// you, don't trigger the property access.
// TODO:
// Maybe we should support EventTarget...
// Should test behavior differences between form element attributes and
// properties...
// Use this to watch property name access.
var map = new Map;
// Use prototype enhancement/pollution so every object gets one.
Object.prototype.onpropertychange = function(propertyName, handler) {
handler = Object(handler);
var fn, context;
if (typeof handler == "function") {
fn = handler;
context = this;
}
else if (typeof handler.handleEvent == "function") {
fn = handler.handleEvent;
context = handler;
}
else {
return false;
}
if (!map.has(this)) {
map.set(this, new Map);
}
var properties = map.get(this);
properties.set(propertyName, this[propertyName]);
Object.defineProperty(this, propertyName, {
get() {
return properties.get(propertyName)
},
set(newValue) {
var previous = properties.get(propertyName);
if (previous === newValue) {
console.log(`should not set "${newValue}"`);
return false;
}
properties.set(propertyName, newValue);
var event = new CustomEvent("propertychange", {
detail: { previous, newValue }
});
fn.call(context, event);
return newValue;
}
});
return true;
};
// We need a way to turn off the access watching...
Object.prototype.stop = function (propertyName) {
if (!map.has(this)) {
return false;
}
var properties = map.get(this);
var value = properties.get(propertyName);
// Remove this property from the observed name list.
properties.delete(propertyName);
// Remove the current property definition to stop observing changes.
delete this[propertyName];
// Re-set this property value to the observed value.
this[propertyName] = value;
return true;
}
/* test it out */
var a = { name: "test" };
console.log(
a.onpropertychange("name", function (event) {
console.warn(this);
console.log(event.detail);
})
);
// true
a.onpropertychange("name", {
handleEvent: function (event) {
console.warn(this);
console.log(event.detail);
},
name: "handleEvent test"
});
// Object { handleEvent: handleEvent(event), name: "handleEvent test" }
a.name = "test";
// should not set "test"
a.name = "updated";
// Object { previous: "test", newValue: "updated" }
var b = Object.assign({}, a);
b.name = 'B';
b.onpropertychange("name", function(event) {
console.log(event);
});
b.name = "Big B";
// propertychange {
// detail: Object { previous: "B", newValue: "Big B" }
// target: null
// }
// Add another listener to A...
a.onpropertychange("name", function (event) {
console.warn("overridden");
console.log(this);
console.log(event.detail);
console.log(this.name);
});
var newName = "Big A";
a.name = newName;
// overridden
// Object { name: Getter & Setter }
// Object { previous: "updated", newValue: "Big A" }
// Big A
console.log(a.name);
// Big A
a.stop('name');
console.assert(a.name === newName, `should preserve name, "${newName}"`);
a.name = "next";
console.log(a.name);
// next
a.name = "overridderrrnnnn";
console.log(a.name);
// overridderrrnnnn
// A form element...
var input = document.createElement("input");
input.defaultValue = "default value";
input.onpropertychange("value", function (e) {
console.log( e );
});
input.setAttribute("value", "another value");
console.log( input.value );
// default value
input.value = "input.value";
// propertychange {
// detail: Object { previous: "default value", newValue: "input.value" }
// target: null
// }
console.log( input.outerHTML );
// <input value="another value">
// An Array...
var a = ['a','b','c'];
a.onpropertychange('length', function (e) {
console.log(e);
});
a.push('d', 'e');
// Uncaught TypeError: can't redefine non-configurable property "length"
// Could be that defineProperty uses get() and set() and therefore cannot use
// configurable: true...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment