Search code examples
javascriptgettersetterdefinepropertypropertydescriptor

Wanting to change a setter of a single input field, how to advance to check if those were changed before and how not to lose the previous addages?


This is a follow up question to Setter for HTMLInputElement.value.

If I changed the setter and getter of a single input-element (not on all input elements in general), and later on I want to make another change (for example done by a third party javascript-module) to this specific input element, how would the second changing work, so that it doesn't override the first change of getter and setter but would just add it sown change?

Specifically or in general: When changing the setter and getter how could the second changing javascript code establish what to change and how to change it without losing the previous addage?

Edit: After the following code, what if I (or a third party module) wants to "inject" a second, a "doSomeLogicWithInput2"-function, into exactly the same input-setter (or getter) with the id "anInputElement"?

    function doSomeLogicWithInput(input) {
        if (input.value.toLowerCase()==='one') {input.style.color='black'; } else { input.style.color='red';} 
    }   
    var inputElement = document.getElementById('anInputElement');

    Object.defineProperty(inputElement, 'value', {
        // HTMLInputElement is like inputElement.prototype.Symbol ???
        set: function(newValue) {
            var valueProp = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
            var valuePropResult = valueProp.set.call(inputElement, newValue);
            doSomeLogicWithInput(inputElement);
            return valuePropResult;
        },
        get : function() {
            var valueProp = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
            return valueProp.get.call(inputElement);
        }
    });
    
            
    // Catch end-user changes
    inputElement.addEventListener('input', function(event) { doSomeLogicWithInput(event.target); }, true);

Edit 2: This is an example how I tried to use Bergi's functions, but it doesn't work really. In the function logic el.value returns 'undefined'.

<html><head>
<script>

function getAccessorDescriptor(obj, prop) {
  if (obj == null) { // only necessary if you may overwrite missing properties
    return {
      get(){},
      set(){},
      enumerable: false,
      configurable: true
    };
  }
  const desc = Object.getOwnPropertyDescriptor(obj, prop);
  if (!desc) {
    return getAccessorDescriptor(Object.getPrototypeOf(obj), prop);
  }
  if ("writable" in desc) { // only necessary if you might overwrite data properties
    return {
      get() { return desc.value; },
      set(v) { if (desc.writable) desc.value = v; else throw new TypeError() },
      enumerable: desc.enumerable,
      configurable: true,
    }
  }
  return desc;
}

function addSetLogic(obj, prop, logic) {
  const oldDescriptor = getAccessorDescriptor();
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  Object.defineProperty(obj, prop, {
    ...oldDescriptor,
    set(value) {
      oldDescriptor.set.call(this, value);
//    ^^^^^^^^^^^^^^^^^
      logic(this, value);
    }
  });
}


var elem; 
function runtest() {
    elem = document.getElementById('mycolor');
    addSetLogic(elem, 'value', logic);
    elem.value='bar';
}
function logic(el,newValue) {
    console.log(el,el.value);  // <- el.value is undefined
    if (el.value==='standard') { el.style.color='black;'}
    else { el.style.color='red'; }
}

</script>
</head>
<body onload="runtest();">
<input id="mycolor" type="text" value="foo">
</body>
</html>

Solution

  • Don't get the Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value') every time you access the property, and also don't get it from HTMLInputElement.prototype but rather from the inputElement itself if there already is one. Rather use the one you have replaced/overwitten with the new descriptor:

    function getAccessorDescriptor(obj, prop) {
      if (obj === null) { // only necessary if you may overwrite missing properties
        return {
          get(){},
          set(){},
          enumerable: false,
          configurable: true
        };
      }
      const desc = Object.getOwnPropertyDescriptor(obj, prop);
      if (!desc) {
        return getAccessorDescriptor(Object.getPrototypeOf(obj), prop);
      }
      if ("writable" in desc) { // only necessary if you might overwrite data properties
        return {
          get() { return desc.value; },
          set(v) { if (desc.writable) desc.value = v; else throw new TypeError() },
          enumerable: desc.enumerable,
          configurable: true,
        }
      }
      return desc;
    }
    
    function addSetLogic(obj, prop, logic) {
      const oldDescriptor = getAccessorDescriptor(obj, prop);
    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      Object.defineProperty(obj, prop, {
        ...oldDescriptor,
        set(newValue) {
          oldDescriptor.set.call(this, newValue);
    //    ^^^^^^^^^^^^^^^^^
          logic(this, newValue);
        }
      });
    }
    

    Then you can do

    addSetLogic(inputElement, 'value', doSomeLogicWithInput);
    addSetLogic(inputElement, 'value', doSomeLogicWithInput2);
    

    as often as you want.