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:
- Browser checks whether the form can be submitted (document fully active, sandboxing etc.), failing the check cancels the algorithm
- Validation defined by the elements' attributes is executed, if validation fails the algorithm is cancelled
- The attached submit event(s) are executed (attached event(s) can fire only once)
- If any of the submit handlers called
event.preventDefault
the algorithm is cancelled - The encoding of the form is set
- An entry list of the form control elements is created
- Various attributes of the form are parsed (ex. action defaults to the URL of the form's document if the attribute is not set)
- The internal sending method is selected based on
method
attribute of the form (defaults to GET) and the schema of the URL inaction
attribute - A navigation plan is created (a task) and send to the event queue (includes the deformed form data)
- 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 tohref
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:
<input type="submit" value="Submit">
<input type="image" alt="Submit">
<button type="submit">Submit</button>
<button>Submit</button>
- Hitting ENTER on active
<input type="date/number/password/search/tel/text/time/url/week">
- 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 ofxhr.open
action
attribute is the second argument ofxhr.open
enctype
attribute is created byFormData
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 theitem
method on theelements
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:
If name is one of the supported property names of the object returned by the
elements
attribute, then run these substeps:
Let candidate be the object returned by the
namedItem()
method on the object returned by theelements
attribute when passed the name argument.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.Return candidate and abort these steps.
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 theDocument
, 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
Input Value Is a String Instead of a Number
Getting Binary (Base64) Data from Html5 Canvas (Readasbinarystring)
Convert HTML to Data:Text/Html Link Using JavaScript
How to Automatically Reload a Web Page At a Certain Time
How to Make Internet Explorer Emulate Pointer-Events:None
Only Detect Click Event on Pseudo-Element
Add Active Navigation Class Based on Url
Disable Drag and Drop on HTML Elements
Force Link to Open in Mobile Safari from a Web App With JavaScript
Which Equals Operator (== VS ===) Should Be Used in JavaScript Comparisons
Changing Element Style Attribute Dynamically Using JavaScript
JavaScript Dom: Find Element Index in Container
How to Change the Background Color With JavaScript