Fixing A 10-Year Gap In My Understanding Of How Form.submit() And Other DOM-Methods Work
Ten years ago, jQuery revolutionized the way that I approached web-development. jQuery did so many things right; but, for me personally, it was the ability to easily bind event-handlers that truly changed my outlook forever. But, it also shaped my mental model for how the DOM (Document Object Model) works. And, as it turns out, my 10-year-old mental model is wrong. Or, perhaps more accurately, incomplete. The DOM provides methods for acting on elements, like .submit(), .focus(), .blur(), and .click(). I had assumed that these were all the same. But, they are not - the .submit() method is different from the rest.
Run this demo in my JavaScript Demos project on GitHub.
When jQuery disrupted the field of web development, one of the common use-cases was to intercept Form Submission events, perform client-side validation, and then submit the form if the inputs were all valid. Such a workflow might look something like this:
jQuery( "form" ).submit(
function( event ) {
event.preventDefault();
// If the form inputs are all valid, submit the form programmatically.
if ( isFormValid( this ) ) {
this.submit();
}
}
);
As you can see, I'm intercepting the "submit" event and preventing the default behavior (which is to POST the form to the server). Then, I perform some sort of client-side validation. And, if all looks good, I re-initiate the form-submission, sending the validated data to the server.
If you'd never seen code like this before, you might wonder how this doen't get caught in an infinite loop? After all, within my "submit" event-handler, I'm triggering a "submit" event. How does the programmatic .submit() not turn around a trigger the very same "submit" event-handler?
To explain the viability of this workflow (to not fall into an infinite loop), I created a mental model that differentiated "user triggers" from "programmatic triggers". In that mental model, user interactions would trigger event-handlers; but, programmatic interactions, like calling form.submit(), would not trigger event-handlers. And, this is the mental model that I've had for the last decade. Unfortunately, it's completely wrong.
I had assumed that the form element's .submit() method was standard and could be used as an example of how the DOM works in general. But, as I've just learned, the .submit() method is much more the exception than the rule. In fact, all the other DOM event-methods that I've tested (focus, blur, click, reset) all cause DOM-events to be triggered even when invoked programmatically.
To demonstrate this, I've put together a simple demo that binds to various events and then attempts to trigger those events using the relevant DOM-methods. In this demo, I happen to be using Angular; but that is irrelevant - it just happens to be the setup I'm used to creating these days:
// Import the core angular services.
import { Component } from "@angular/core";
// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //
@Component({
selector: "my-app",
styleUrls: [ "./app.component.less" ],
template:
`
<p>
In the following examples, I am explicitly calling DOM-methods that trigger
events; however, you will see that <code>form.submit()</code> does not work
like the other methods (focus, blur, click).
</p>
<p>
<input #ref1 (focus)="logEvent( 'Focus event handled.' )" /> —
<a (click)="ref1.focus()">Focus input</a>
</p>
<p>
<input #ref2 (blur)="logEvent( 'Blur event handled.' )" (focus)="ref2.blur()" />
</p>
<p>
<span #ref3 (click)="logEvent( 'Click event handled.' )">X</span> —
<a (click)="ref3.click()">Click span</a>
</p>
<form #ref4 (reset)="logEvent( 'Reset event handled' )">
<a (click)="ref4.reset()">Reset form</a>
</form>
<form #ref5 action="#hashy" (submit)="logEvent( 'Submit event handled' )">
<a (click)="ref5.submit()">Submit form</a> —
Compare with the <input type="submit" value="Submit Button" />
</form>
`
})
export class AppComponent {
// I log the given message to the console.
public logEvent( message: string ) : void {
console.log( message );
}
}
In each of these examples, I'm exporting a DOM Element using the Angular #ref micro-syntax. Then, within an unrelated event-handler, I'm calling one of the native DOM-methods on said #ref. And, when we go down the list of examples, we get the following console-logging:
As you can see, every DOM-method that we invoked - .focus(), .blur(), .click(), .reset() - triggered a DOM-event. All with the exception of the .submit() method, which did not trigger a DOM-event when invoked programmatically.
It's always a little depressing when you find out that your "truths" turn out to be invalid. But, it's exciting to finally have a more accurate mental model. In my case, I had assumed that the Form Element's .submit() method behavior was standard. It turns out not to be. At least not standard when compared to other DOM Element methods like .blur() and .click(). Of course, in the long run, the best approach is probably to look at the documentation and see how a particular DOM method is supposed to work. But for now, I'm feeling much better about my understanding of web development.
Want to use code from this post? Check out the license.
Reader Comments
Reading back through my old posts, it looks like I was also struggling with this as recently as 7-years ago:
www.bennadel.com/blog/2075-learning-event-driven-programming-best-practices-from-web-browsers.htm
... in that post, I didn't understand the behavior I was seeing -- that the .focus() method will only trigger a "focus" event if the input is actually changing state. As such, I was never able to roll that into a better mental model.
Oh well, better late than never.
@All,
One point of clarity that I wanted to make, which I don't think I did in the post is that the DOM-methods don't always lead to DOM-events. For example, if I call .focus() on an element that is already focused, then there is no subsequent "focus" event. Same with the "blur" event.