Let's say I have a class which stores the properties of its instances in a nested object:
this.Properties = {
"Position":{
"X": 400,
"Y": 100
},
"Colour": "#007fff7f"
};
I wanted to define special getters/setters for each of the (nested) properties so that I could add range checks / automatically update the properties of instance-specific HTML elements, etc. When I tried it with the normal method, I realised that I couldn't bind the scope to an argument in the getters/setters:
//(based on https://stackoverflow.com/a/16400626)
//Define function prototype for binding an argument without overriding the old this:
Function.prototype.BindArgs = function(...boundArgs){
const targetFunction = this;
return function (...args) { return targetFunction.call(this, ...boundArgs, ...args); };
};
//...
{
get X(){
return this.__X__;
},
set X(Scope, Value){
this.__X__ = Value;
Scope.HTMLElement.style.left = Value + "px";
}.BindArgs(this) //This is incorrect syntax
}
The above code doesn't run: not because BindArgs is an invalid prototype, but instead, it doesn't work because the setter isn't actually a function. The answer suggested to use Object.defineProperty, which actually worked:
Object.defineProperty(this.Properties.Position, "X", {
"get": function(){
return this.__X__;
}
"set": function(Scope, Value){
this.__X__ = Value;
Scope.HTMLElement.style.left = Value + "px";
}.BindArgs(this)
});
Now, when I've got a few properties like in the example above, this would be fine, but having to do this for dozens of properties becomes extremely tedious - especially for nested properties. Is there another, tidier way, of defining custom getters/setters and being able to bind arguments to them? The normal syntax would've been ideal since it would all be inside of the object definition and not scattered around like Object.defineProperty. The obvious answer would be to use normal functions to get/set the values, but doing that would mean having to refactor a lot of code...
I suggest you use Proxies for validation. It requires very minimal code changes and you can take care of multiple properties in one fell swoop.
let validator = {
set: function(obj, prop, value) {
//in any of these cases you can return false or throw an error to refuse the new value
switch(prop) {
case "X":
Scope.HTMLElement.style.left = value + "px";
break;
case "Y":
Scope.HTMLElement.style.top = value + "px";
break;
case "Colour":
Scope.HTMLElement.style.color = value;
}
obj[prop] = value;
return true;
}
};
this.Properties.Position = new Proxy(this.Properties.Position, validator);
this.Properties = new Proxy(this.Properties, validator);
Note that this uses a shortcut (the same validator for both Properties
and Properties.Position
), if you find you may have property name overlaps you may need multiple validator
objects.