Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to overwrite focus() in JavaScript for all elements?

Tags:

javascript

I want to overwrite the function focus() for all HTML elements in JavaScript.

How do I do that?

like image 891
Timo Ernst Avatar asked Oct 31 '12 14:10

Timo Ernst


1 Answers

The question asks for just how to override the .focus() method for HTML elements, so that will be answered first. There are however, other ways to trigger "focus" on elements, and overriding those methods are discussed below.

Please also note, it is probably not a good idea to override global prototypes.


Overriding the .focus() method on HTML elements

You can override the focus function for all HTMLElement's by modifying its prototype.

HTMLElement.prototype.focus = function () {
  // ... do your custom stuff here
}

If you still want to run the default focus behavior and run your custom override, you can do the following:

let oldHTMLFocus = HTMLElement.prototype.focus;
HTMLElement.prototype.focus = function () {
    // your custom code
    oldHTMLFocus.apply(this, arguments);
};

Overriding the focus event set via handlers on HTML elements

This is done in a similar fashion to overriding the .focus() method. However, in this case, we will target the EventTarget constructor and modify the addEventListener prototype:

let oldAddListener = HTMLElement.prototype.addEventListener;
EventTarget.prototype.addEventListener = function () {
    let scope = this;
    let func = arguments[1];

    if (arguments[0] === 'focus') {
        arguments[1] = function (e) {
            yourCustomFunction(scope);
            func(e);
        };
    }

    oldAddListener.apply(this, arguments);
};

If you don't want original behavior at all, you can remove the func call (func(e)).


Overriding the focus event set via the onfocus attribute

Doing this is actually quite tricky, and will most likely have some unforseen limitations. That said, I could not find a way to override this by modifying prototypes, etc. The only way I could get this work was by using MutationObservers. It works by finding all elements that have the attribute onfocus and it sets the first function to run to your override function:

let observer = new MutationObserver(handleOnFocusElements);
let observerConfig = {
    attributes: true,
    childList: true,
    subtree: true,
    attributeFilter: ['onfocus']
};
let targetNode = document.body;

observer.observe(targetNode, observerConfig);

function handleOnFocusElements() {
    Array
        .from(document.querySelectorAll('[onfocus]:not([onfocus*="yourCustomFunction"])'))
        .forEach(element => {
            let currentCallbacks = element.getAttribute('onfocus');
            element.setAttribute('onfocus', `yourCustomFunction(this); return; ${currentCallbacks}`);
        });
}

If you want to stop the original onfocus from firing its events completely, you can just empty the onfocus attribute entirely on any mutation changes, and just set the value to your desired function.


An example with all of the code snippets together:

(function() {

  window.yourCustomFunction = function(target) {
    console.log('OVERRIDE on element', target);
  };

  let observer = new MutationObserver(handleOnFocusElements);

  let observerConfig = {
    attributes: true,
    childList: true,
    subtree: true,
    attributeFilter: ['onfocus']
  };

  let targetNode = document.body;

  // Start overriding methods
  // The HTML `.focus()` method
  let oldHTMLFocus = HTMLElement.prototype.focus;
  HTMLElement.prototype.focus = function() {
    yourCustomFunction(this);
    oldHTMLFocus.apply(this, arguments);
  };

  // Event Target's .addEventListener prototype
  let oldAddListener = HTMLElement.prototype.addEventListener;
  EventTarget.prototype.addEventListener = function() {
    let scope = this;
    let func = arguments[1];

    if (arguments[0] === 'focus') {
      arguments[1] = function(e) {
        yourCustomFunction(scope);
        func(e);
      };
    }

    oldAddListener.apply(this, arguments);
  };

  // Handle the onfocus attributes
  function handleOnFocusElements() {
    Array
      .from(document.querySelectorAll('[onfocus]:not([onfocus*="yourCustomFunction"])'))
      .forEach(element => {
        let currentCallbacks = element.getAttribute('onfocus');
        element.setAttribute('onfocus', `yourCustomFunction(this); return; ${currentCallbacks}`);
      });
  }

  window.addEventListener('load', () => {
    handleOnFocusElements();
    observer.observe(targetNode, observerConfig);
  });
})();

let input = document.querySelector('input');

input.addEventListener('focus', function() {
  console.log('Add Event Listener Focus');
});

function attributeHandler() {
  console.log('Called From the Attribute Handler');
}
<input type="text" onfocus="attributeHandler()">
like image 62
KevBot Avatar answered Sep 18 '22 17:09

KevBot