3

I have been looking for a way to permanently change the HTMLFormElement Javascript object's 'onsubmit' behavior. Let's suppose that one of my Javascript would contain the following code:

HTMLFormElement.prototype.onsubmit = function () {
    this.submit();
    // Make sure that you can only submit once.
    this.onsubmit = function () {
        return false;
    };
    return false;
};

The idea would be to simply prevent double submits on all forms of the document. I wasn't able to figure out how to do this since the above example is not working.

Is there a way to achieve this without looping through all existing HTMLElements after the document is loaded? A way which would be compatible with some older browser and also persist the behavior if you create new form elements using other scripts. (Vanilla JS only please)

Tomáš Zato
  • 50,171
  • 52
  • 268
  • 778
Nicolas Bouvrette
  • 4,295
  • 1
  • 39
  • 53
  • 7
    Wouldn't it be easier to just write code that doesn't submit your forms twice, instead of changing native prototypes ? – adeneo Dec 19 '15 at 23:24
  • use event delegation to have a single document event hit all forms, no under-the-hood monkeying required. – dandavis Dec 19 '15 at 23:24
  • @adeneo Actually this is a simplified example of something more complex which would require something similar to this to keep simple. – Nicolas Bouvrette Dec 19 '15 at 23:25
  • @dandavis Do you have some examples? – Nicolas Bouvrette Dec 19 '15 at 23:26
  • 1
    I tried and even multiple `.submit()` or clicking a submit button multiple times only submitted the form once. – Oriol Dec 19 '15 at 23:26
  • 2
    `document.addEventListener("submit", function(e){ var el=e.target; if(el.spent) return e.preventDefault(); el.spent=true; })` – dandavis Dec 19 '15 at 23:27
  • 2
    @dandavis Don't `return false` in an event listener, that's only for event handlers. Use `e.preventDefault()`. – Oriol Dec 19 '15 at 23:28
  • 1
    How older is "compatible with some older browser"? Why do you have [ES6] tag if you want compatibility with older browsers? Are you trying to prevent form submission done by the user (through submit button or implicit submission), or done by you (through `.submit()`)? Are you using `target` in your form? – Oriol Dec 19 '15 at 23:44
  • What about other scripts that use `form` elements? They most likely won't anticipate your default behavior. – MinusFour Dec 19 '15 at 23:58
  • What are you going to do if the form submit has failed? User cannot try to do it again. So, good practice in here is temporary to disable "submit" button and enable it after success send. It may be more complex and difficult but it really solves the problem instead of hiding only one effect. – just-boris Dec 20 '15 at 13:14

4 Answers4

5

Can we overwrite Javascript DOM object prototype properties?

Although the javascript spec cuts host objects a lot of slack in violating normal javascript behavior Object.defineProperty(HTMLFormElement.prototype, "onsubmit", {get: () => {return false;}}) does work. "Work" as in setting a getter on the prototype.

But it's not actually what you want. That's just replacing the getter/setter functionality. There is no guarantee that the browser goes through the javascript inheritance chain to retrieve the handler for the current form.

Either because it bypasses javascript by directly looking at some underlying native properties or because it doesn't have to look at the prototype at all and looks at an instance property of the form itself.

Messing with prototypes is rarely a good idea if you don't know how and when individual properties are actually used by the target class.

Another thing: the getter/setter actually is on HTMLElement.prototype, not HTMLFormElement.prototype. At least that's where it is in Firefox. You should at least inspect the inheritance chain before trying to mess with it ;)

The idea would be to simply prevent double submits on all forms of the document.

Instead of messing with prototypes you can simply install a capturing event listener on the document.

capturing because it happens earlier and is less likely to be intercepted via stopPropagation without canceling the default action, which would be the form submission this case.

document.addEventListener("submit", (e) => {e.preventDefault();}, true);
the8472
  • 40,999
  • 5
  • 70
  • 122
  • The submit event actually propagates to the document? I thought someone in comments claimed it does not, otherwise that would be the ideal solution. – Tomáš Zato Dec 20 '15 at 13:36
  • By the way I tried the defineProperty trick on both Chrome and Firefox and it didn't seem to behave as expected. I'm not sure there is actually a way to get to what I was hoping without going through all elements. Also the event listener trick does not work in all browsers. – Nicolas Bouvrette Dec 20 '15 at 21:02
  • I said the prototype thing is not what you want, I just answered that part of the question first. – the8472 Dec 21 '15 at 07:49
0

How about this?

[].forEach.call(document.getElementsByTagName('form'), function(form) {
  form.onsubmit = function () {
    // Make sure that you can only submit once.
    this.onsubmit = function () {
      return false;
    };
    return true;
  };
});
Louay Alakkad
  • 7,132
  • 2
  • 22
  • 45
0

tl;dr: Nope, do as @Louy says, it's correct. DOM was designed to be looped through, no worries.


Yeah, you can overwrite HTMLElement properties and it's same for HTMLElement descendants. But specifically for some properties, like event callbacks, you're not allowed to do that, because they are not normal properties but getters and setters. I get the following error for your code, which is probably what you meant by "doesn't work":

image description

Even if you achieved to do that, I'm not sure if the resulting behavior is defined by standards. I think this would definitely not lead to anything compatible between browsers.

My experience also shows that globally changing some API because you need it for specific functions is not a good idea - soon your request might change and you will be changing whole API in general.

Tomáš Zato
  • 50,171
  • 52
  • 268
  • 778
  • Again the concern was that if you create a new form (e.g.: var newForm= document.createElement('form');) you will have to re-apply the same logic if you want this behavior to be standard accross your site. This behavior was also very unintrusitve which is why I was looking for something simple. But thanks for the details, I understand at least why I cannot overwrite those properties! – Nicolas Bouvrette Dec 19 '15 at 23:57
  • @NicolasBouvrette Trust me, I thought hard before posting answer that doesn't fullfill your request. I like to say that in programming, nothing is impossible. Unfortunatelly, it's not 100% true. – Tomáš Zato Dec 19 '15 at 23:59
  • @NicolasBouvrette, you can just as easily create a factory function that creates the `form` element and attach the listener, now you just call that function instead of `document.createElement('form')`. – MinusFour Dec 20 '15 at 00:05
  • @MinusFour Yes I have something like that in mind already - thanks for the suggestion! – Nicolas Bouvrette Dec 20 '15 at 00:08
  • @MinusFour I also thought of proposing `.createElement` ovrriding. But there are other ways of creating elements, like parsing HTML (`x.innerHTML = "
    – Tomáš Zato Dec 20 '15 at 00:10
  • @TomášZato, you can also create a wrapper for that. Although I see your point, you'd have to anticipate every possible way to create the `form` element. Then again, it's only you who would need to deal with these custom functions, since it's you who is interested in overriding the defaults. – MinusFour Dec 20 '15 at 00:14
  • @MinusFour I feel strong urge to bet with you that you won't be able to override all the apis :) Also I have some performance concerns about overriding some of them. It's an interesting thing for fun, but definitelly not a good idea in serious business. – Tomáš Zato Dec 20 '15 at 00:21
0

Yes, you can overwrite JavaScript DOM object prototype properties. However, you should never ever ever ever... EVER do this! That is, unless you're writing a polyfill that is supposed to add standard behavior so browsers that don't support it. Unless you're in that very specific and fairly rare situation, you're not supposed to ever modify objects you don’t own!

For your specific use case, you can easily achieve the desired effect by using event.preventDefault() on every click of the submit button except for the first click :

var alreadySubmitted = false;

document.getElementById("submitbutton").addEventListener("click", function(event){
    if(!alreadySubmitted) {
        // The first time the button is clicked
        alreadySubmitted = true;
    } else {
        // All subsequent times the button is clicked
        event.preventDefault();
    }
}, false);
John Slegers
  • 45,213
  • 22
  • 199
  • 169