Reputation: 23416
I've a form with few textfields and a submit button. A submit handler is attached to the form in order to validate the form before submission. Additionally, the handler is supposed to show an OK message to the user, and finally to redirect to the front page.
Validation works fine, but when the validation succeeds, the OK message is shown only briefly or not at all, and the page is refreshed instead of redirection.
Here is the form:
<form id="form" method="post">
<input name="firstname">
<input name="lastname">
<input type="submit" value="Submit">
</form>
<div class="hidden v-ok">Validation OK</div>
<div class="hidden v-failed">Validation failed</div>
And the related JS:
const form = document.querySelector('#form');
form.addEventListener('submit', e => {
const controls = Array.from(form.elements),
valid = controls.every(control => control.value !== '');
if (!valid) {
// Validation failed, don't submit
e.preventDefault();
// Show ValidationFailed message
const message = document.querySelector('.hidden.v-failed');
message.classList.remove('hidden');
return;
}
// Validation OK, show message and submit
const message = document.querySelector('.hidden.v-ok');
message.classList.remove('hidden');
window.setTimeout(() => {
window.location.href = '/';
}, 2000);
});
I've also tried to redirect without the timeout, but the result is the same, the page is only refreshed, it never navigates to the front page. What can I do to get my form posted and still able to see the message and do the redirection?
Upvotes: 4
Views: 2468
Reputation: 23416
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.
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:
event.preventDefault
the algorithm is cancelledmethod
attribute of the form (defaults to GET) and the schema of the URL in action
attributeIn 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.)
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 linklocation.href
in JS) is prevented while a submit event is pendingWhat 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>
<input type="date/number/password/search/tel/text/time/url/week">
form.submit
method in JSNotice the typeless <button>
element, it's also a submit button by default, when placed inside a form.
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.
In the past, the first three elements in the submitter element list above were "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 didn't prevent the form submission. Other elements could do that also within a click
listener. This behavior has been changed after this answer was written, modern browsers can prevent form submission also in a click handler of the any submitter element. To prevent the submission in all cases (also in older browsers), 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.
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:
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.
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.
Upvotes: 18