Understanding HTML Form Element Behavior

Understanding HTML Form Element behavior

TL;DR; Showing the message and redirecting later provides the document location to stay on the current page. You can't submit a form and stay on the current page. Use AJAX aka XMLHttpRequest to send the data to your server instead of submitting a form.

How HTMLFormElement works

On early days, before JavaScript existed, form element was purposed to send data from a page to a server, then the server handled the data, and responded with a new page, which was loaded by the browser. Even today, browsers are doing exactly this same when a form is submitted.

The standard says:

"The form element represents a hyperlink that can be manipulated through a collection of form-associated elements" (emphasis mine).

Now, if you're not interested in a deeper explanation of how the form submission works under the hood, you can scroll down to the All this in practice chapter.

But what actually happens when a submit button is clicked? The standard defines a Form submission algorithm, which is very deeply detailed, and is a bit hard to follow. Here are the strongly simplified main steps:

  1. Browser checks whether the form can be submitted (document fully active, sandboxing etc.), failing the check cancels the algorithm
  2. Validation defined by the elements' attributes is executed, if validation fails the algorithm is cancelled
  3. The attached submit event(s) are executed (attached event(s) can fire only once)
  4. If any of the submit handlers called event.preventDefault the algorithm is cancelled
  5. The encoding of the form is set
  6. An entry list of the form control elements is created
  7. Various attributes of the form are parsed (ex. action defaults to the URL of the form's document if the attribute is not set)
  8. The internal sending method is selected based on method attribute of the form (defaults to GET) and the schema of the URL in action attribute
  9. A navigation plan is created (a task) and send to the event queue (includes the deformed form data)
  10. The task in the event queue is executed, and navigation to a new page takes place

In the real algorithm, a lot more of actions are executed, ex. step 1 is executed between many of the steps, and the algorithm prevents concurrent submissions. Also, the described algorithm stands only when an actual submit button is activated. If the submission is called via JS by form.submit method, steps 2 - 4 are jumped over.

The above algorithm explains how JS form validation in submit event handler can cancel the submission (#4). The unload document process (#10) explains how the timeout is broken when a new page is about to load (all the pending timers and events are aborted). But, any of these doesn't actually explain, how location.href in a submit event handler is ignored without the timeout.

The answer to this question is hidden in the definition of the form: It "represents a hyperlink". Activating a hyperlink sets a specific internal navigation event, and an internal "ongoing-navigation" flag. Any later activation of any hyperlink checks that flag, and is cancelled if the flag was set. Setting location.href is not actually a hyperlink, but it uses the same navigation mechanisms, hence it's also cancelled, if a pending navigation is detected. It's notable, that calling event.preventDefault in a submit handler unsets the "ongoing-navigation" flag immediately, which makes setting location.href to work again later in the handler.

(The navigation behavior described above is heavily simplified. To fully understand how the browser navigation works, provides deep knowledge of the event queues and navigation process, which are described in the standard (details being even partially implementation-dependent), but these are not the objective of this answer.)

All this in practice

As the most of the previously described actions of HTML Form are executed under the hood, how can this all be applied in the point of the view of a programmer? Let's make a synthesis (without specific rationales).

  • <form> is a link which can send more information to the server than a regular link like <a>
  • action attribute of a form is roughly equal to href attribute of a regular link
  • Submitting a form always loads a new page, this is the default action of the submission
  • Any attempt to navigate to another page (including clicking links or submitting other forms, or setting location.href in JS) is prevented while a submit event is pending
  • Programmer can interfere form submission by setting a submit event listener to the form
  • If attribute-based form validation fails, attached submit event handler(s) is/are not executed
  • A submit event handler can modify the current page and the data to submit, and it also can cancel the form submission
  • Nevertheless what is done in a submit event handler, the form is submitted when the handler execution is finished, unless the handler cancels the default action of the submit event

What triggers a form submission then? There are multiple ways to start a form submission. All the elements below are so called submitter elements, those have "submit the form" as their default action:

  1. <input type="submit" value="Submit">
  2. <input type="image" alt="Submit">
  3. <button type="submit">Submit</button>
  4. <button>Submit</button>
  5. Hitting ENTER on active <input type="date/number/password/search/tel/text/time/url/week">
  6. Calling form.submit method in JS

Notice the typeless <button> element, it's also a submit button by default, when placed inside a form.

Preventing the default action of an event

Some events have a default action, which is executed after the event handler function has finished. For example, the default action of submit event is to submit the form triggered the event. You can prevent the default action to be executed in the event handler by calling preventDefault method of the event object:

event.preventDefault();

event is either the object passed in the arguments of the handler, or the global event object.

Returning false from an event handler function doesn't prevent the form submission. This works with inline listeners only, when return false; is written in an onsubmit attribute.

The first three elements in the submitter element list above are "stronger" than the other elements. If you've attached a click listener to an input type of submit/image or a button type of submit, preventing the default action of the (click) event doesn't prevent the form submission. Other elements can do that also within a click listener. To prevent the submission in all cases, you've to listen to submit event on the form instead of clicks on the submitter elements.

And again: form.submit method called in any script, will jump over all form validations and won't fire any submit events, it can't be cancelled via JS. It's notable, that this stands for the native submit method only, ex. jQuery's .submit isn't native, and it calls the attached submit handlers.

Send data and stay on the current page

To send data and still stay on the current page can be achieved only by not submitting the form. There are multiple ways to prevent the submission. The simplest way is to not include a form in the markup at all, that makes a lot of extra work with data handling in JS, though. The best way is to prevent the default action of the submit event, any other way with a form would cause extra work, for example, triggering JS without a submitter element still provides detecting submissions made by #5 in the submitter elements list.

As the data won't go to a server without submitting, you need to send the data with JS. The technique to use is called AJAX (Asynchronous Javascript And Xml).

Here's a simple vanilla JS code example of sending data to the server, and staying on the current page. The code utilizes XMLHttpRequest object. Typically the code is placed in a submit event handler of a form, as it is in the example below. You can make a request anywhere in the scripts on a page, and if a submit event is not involved, preventing the default action is not needed.

form.addEventListener('submit', e => {
const xhr = new XMLHttpRequest();
const data = new FormData(form);

e.preventDefault();

xhr.open('POST', 'action_URL');
xhr.addEventListener('load', e => {
if (xhr.status === 200) {
// The request was successful
console.log(xhr.responseText);
} else {
// The request failed
console.log(xhr.status);
}
});
xhr.send(data);
});

The analogy to a form is scattered allover the code:

  • control element values are included in data (form being a reference to an existing form element)
  • method attribute is the first argument of xhr.open
  • action attribute is the second argument of xhr.open
  • enctype attribute is created by FormData constructor (defaults to "multipart/form-data")

The difference between AJAX and form submission is, that when getting a response for an AJAX call from the server, the JavaScript execution continues in the load handler of xhr instead of browser loading a new page when submitting a form. In that load handler you can do what ever you need with the data server sent as the response, or just redirect (also succesfully with a timeout). It's also possible to leave the load handler out of the code, when you don't need a notify of the request success/failure or any response data.

In modern browsers you can also use Fetch API to make an AJAX request. Also, most of the commonly used frameworks (if not all) and many libraries (like jQuery), have their own (more or less simplified) implementations for various AJAX-based tasks.

Remarks

Due to the order of the tasks executed in the internal submit algorithm, the validation attributes are handled before calling the submit event handler(s). If any of the validations fails, the algorithm is cancelled. This means, that when the form validation fails, also the submit event handler(s) are not executed.

Validation based on the validation attributes is executed only after activating a submitter element, validation attributes are not affecting to an object created by FormData constructor, which can be created at any time, a submit event is not necessarily needed.

The target attribute of the form is ignored when creating a FormData object. An AJAX call always targets to the current page, it can't be redirected to another document.

It's not mandatory to send a FormData object, you can send any data you need, but it has to be formatted according to the method of the request.

understanding the behavior of file upload form element

A button has by default the behavior of submitting.
Source

If you change your button to <button type="button">Button</button> it'll override the default submit behavior.

What is the default behavior of the 'return' key in an HTML form?

Both button elements have no type set. Type=submit "is the default if the attribute is not specified for buttons associated with a <form>" (MDN Webdocs)
and troublesome is the next after the input field. If you change its type to 'button', 'submit' will submit.

From MDN as well: "Note that the submit event fires on the element itself, and not on any or inside it. However, the SubmitEvent which is sent to indicate the form's submit action has been triggered, includes a submitter property, which is the button that was invoked to trigger the submit request. If the submission was not triggered by a button of some kind, the value of submitter is null. (If there's no element type=submit and Enter is pressed inside the input, the form will submit with event.submitter = null)

The logic seems to be "find the closest button to the focus that has type="submit", and simulate a click event on that button, else fall back to just submitting the form" (didn't find this explicitly somewhere)
In your example 'troublesome' is of type 'submit' and closest to the input, and if there wasn't the 'prevent.default()', both events (click & submit) would be triggered.

document.querySelector('form').onsubmit = ev => {
ev.preventDefault();
document.querySelector('#result').innerText
= ev.target.elements.text.value;
};

document.querySelector('#troublesome').onclick =
ev => {
ev.preventDefault();
ev.target.innerText = Math.random();
};
<form>
<input type="text" name='text' value='something' />
<button type="button" id="troublesome">Trouble</button>
<button>Submit</button>
</form>

<div id="result">not submitted</div>

Strange behavior of an input html element with display: block

If you don't want the padding on a grid parent element to effect its children, surround all its children elements in a block element with a class of row.

Bootstrap input elements are meant to span the whole width of there parent elements even without display block style attribute.

<div class="col-md-12">
<div class="row"> <!--this is what you need -->

</div>
</div>

full example code

<div class="col-md-12">
<div class="row">
<input type="text" placeholder='I\'m some damned input' />
</div>
<div class="row">
<div>I am some div</div>
</div>
</div>

Behavior of html form onsubmit event in JSFiddle

You're encountering a design feature (or flaw) in JSFiddle:

In JSFiddle, the "JavaScript" pane is not the direct source code of a referenced JavaScript file, instead JSFiddle wraps that code as below and inserts it into <head>. Just go View Source to see what it does:

<script type='text/javascript'>
//<![CDATA[
window.onload=function(){
function falsifier() {
return false;
}
}
//]]>
</script>

Your <form> elements can't find falsifier because falsifier only exists within the scope of this anonymous function.

Form elements as properties of the form

Found it in the draft HTML5 specification:

When a form element is indexed for indexed property retrieval, the user agent must return the value returned by the item method on the elements collection, when invoked with the given index as its argument.

Each form element has a mapping of names to elements called the past names map. It is used to persist names of controls even when they change names.

The supported property names are the union of the names currently supported by the object returned by the elements attribute, and the names currently in the past names map.

When a form element is indexed for named property retrieval, the user agent must run the following steps:

  1. If name is one of the supported property names of the object returned by the elements attribute, then run these substeps:

    1. Let candidate be the object returned by the namedItem() method on the object returned by the elements attribute when passed the name argument.

    2. If candidate is an element, then add a mapping from name to candidate in the form element's past names map, replacing the previous entry with the same name, if any.

    3. Return candidate and abort these steps.

  2. Otherwise, name is the name of one of the entries in the form element's past names map: return the object associated with name in that map.

If an element listed in the form element's past names map is removed from the Document, then its entries must be removed from the map.

...and we know from the DOM2 HTML specification's section on ECMAScript language mapping that indexing via [] ends up being a call to either item (for numbers) or namedItem (for strings).

Thanks to @Carey for his answer to my other related question, which made me facepalm and look in the HTML5 draft, since they're trying to subsume the HTML DOM stuff into the spec.

HTML Form Tags, what is the actual default event or behavior?

According to the W3 standard, the default for method is the GET method. The action field is mandatory so if the browser does not know where to redirect the query, probably nothing will be send.

Precedence when HTML form element attributes and properties conflict

Take at look at http://jsfiddle.net/Ldf2s/9/.

<form name="foo">
<input name="bar" type="checkbox" />
<input name="baz" type="text"/>
</form>

<script>
setTimeout(function() {
document.forms['foo'].bar.setAttribute("checked", "true");
}, 5000);

setTimeout(function() {
document.forms['foo'].baz.setAttribute("value", "attr value");
}, 5000);
</script>

If you run the page and don't touch the input boxes, after 5 seconds the checkbox will be ticked and value will be set. But if you run the page and immediately click on the checkbox and type some text in the text input, the values will not be modified once 5 seconds has elapsed.

The concepts to be understood here are the dirty checkedness flag and the dirty value flag.

What these do is to mark the checkbox and text input fields as to whether document.forms['foo'].bar.setAttribute("checked", "true"); and document.forms['foo'].baz.setAttribute("value", "attr value"); affect the checkedness and value of the respective inputs or not.

For the dirty checkedness flag, the HTML5 spec says

Each input element has a checkedness, which is exposed by the checked IDL attribute.

Each input element has a boolean dirty checkedness flag. When it is true, the element is said to have a dirty checkedness. The dirty checkedness flag must be initially set to false when the element is created, and must be set to true whenever the user interacts with the control in a way that changes the checkedness.

The checked content attribute is a boolean attribute that gives the default checkedness of the input element. When the checked content attribute is added, if the control does not have dirty checkedness, the user agent must set the checkedness of the element to true; when the checked content attribute is removed, if the control does not have dirty checkedness, the user agent must set the checkedness of the element to false.

So in the JSFiddle above the act of the user clicking the checkbox stops the setAttribute call affecting whether the checkbox is checked or not.

However, it also says that

The checked IDL attribute allows scripts to manipulate the checkedness of an input element. On getting, it must return the current checkedness of the element; and on setting, it must set the element's checkedness to the new value and set the element's dirty checkedness flag to true.

So document.forms['foo'].bar.checked=false; should also have the effect of stopping the setAttribute call from setting the checked state because it too should make the dirty checkedness flag true.

But in Chrome the checked value is changed, so it is inconsistent with the HTML5 spec.


The HTML5 spec also has an analogous requirement for the text input value in that there is a dirty value flag that is set true when the user changes the value, or the value property is set programatically.

It seems in this case Chrome behaves correctly, possibly because the requirement to set the dirty value flag when the value property is set programatically is made explicitly when describing the dirty value flag, whereas the equivalent description of the dirty checkedness flag does not have the matching comment. That requirement is only given separately in the quote above.

jquery.html() strange behavior with form

This is not a strange jQuery behavior, its a strange DOM effect. jQuery.val() does nothing else than setting the value property of <input> element. By "property", I mean the DOM property and not the node attribute - see .prop() vs .attr() for the difference.

The .html() method, which returns the innerHTML serialisation of the DOM, is expected to show only attributes of the elements - their properties are irrelevant. This is the default behaviour, and when you want input values serialized you need to explicitly set them as attributes - $input.attr("value", $input.prop("value")).

So why did simple val() work on the hidden input elements? The reason is the HTML specification. There are reflecting IDL attributes, where the DOM property is coupled with the node attribute, but the value attribute is none of those. Yet, the value IDL attribute has special modes, in which it reacts differently. To cite the spec:

The attribute is in one of the following modes, which define its behavior:

value

On getting, it must return the current value of the element. On setting, it must set the element's value to the new value, set the element's dirty value [… and do a lot of other stuff].


default

On getting, if the element has a value attribute, it must return that attribute's value; otherwise, it must return the empty string. On setting, it must set the element's value attribute to the new value.


["default/on" and "filename" modes]

Spot the difference? And now, let's have a look at the states of the type attribute. And really, if we check the Bookkeeping details sections, we can notice that in the hidden state,

The value IDL attribute applies to this element and is in mode default

‐ while in all other (textual) states the mode is "value".


TL;DR:

(Only) On <input type="hidden"> elements, setting the value DOM property (input.value = …, $input.val(…), $input.prop("value", …)) also sets the value attribute and makes it serializable for innerHTML/.html().



Related Topics



Leave a reply



Submit