Understanding HTML Form Element behavior - javascript

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?

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 in action 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 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:
<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 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.

Related

Allow HTML form validation but stop submit once complete

I know a way to stop a form from submitting, but i have a on click event to the submit button and its firing even though the form doesnt pass the HTML validation.
<form id="signupform" class="signupform" onsubmit="(e)=>{e.preventDefault()};return false">
</form>
My goal is to stop the page refresh either way (if it validates or not) but still allow the built in validation to run first.
Any suggestions?
A submit button's job is to trigger the submit event of a form. Therefore, with form elements, you don't set up click events on the submit button, you set up submit event handlers on the form.
Then, to introduce validation into the mix, you can stop the native submit to take place in the handler, only if validation fails. This is done by accessing the event argument that is automatically sent to every DOM event handler* (see next paragraph for caveat). You can use the event.preventDefault() method to stop the native event from taking place.
*One final note, the use of inline HTML event handling attributes such as onsubmit and onclick is to be avoided. This is a 25+ year old technique that we used before we had standards and unfortunately, because they seem easy to use, they get copied by new developers who don't know any better. There are real reasons not to use them and you've stumbled into one. Your e argument to your event handling function is not being populated with a reference to the event like you think it is. That only happens when you use the modern standard way of setting up event callbacks, which is .addEventListener().
// Set up a submit event handler for the form
// not a click event handler for the button because
// clicking a submit button triggers the form's submit event
document.querySelector("form").addEventListener("submit", function(event){
if(document.querySelector("input").value === ""){
// Invalid data! Stop the submit!
event.preventDefault();
alert("Please fill in all fields!");
return;
}
// If the code reaches this point, validation succeeded
console.log("Form submitted");
});
<form action="https://example.com" method="post">
<input>
<button>Submit</button>
</form>

Calling JavaScript function on hitting enter key in input box

Although I am able to call Javascript function on hitting the enter key. I am calling a function shortIt() when user hits the enter key, shortIt() takes the text from input box and makes a request to Google URL Shortener API which returns a short URL, but the generated response is visible for only some seconds.
I am showing the response in a div.
Seems to be very weird problem, code is here https://s3-ap-southeast-1.amazonaws.com/s3freebucket/URLShortner/url-shortner.html
But when I click on shortIt button to short the url. It works fine and the response text stays in the div.
This is because on pressing the enter key, the form gets submitted. When this happens you will notice the page reloading. This is a default behaviour and is the consequence of using <form>. On form submission, the page in the forms action attribute is loaded. In this case the action attribute of the <form> is not set and so the page you are on is just reloaded, thus the script stops running and the page reloads.
Two straight forward options for fixing this:
(Simplest) Remove the form tag - it is not used to submit anything so removing it should leave things still working
Prevent the default action when the form submit event is fired.
With JQuery:
$('#myForm').on('submit', function (evt) {
evt.preventDefault(); // prevents form submission
});
Where myForm is the ID of your form tag. You will need add an id attribute to your existing html form tag: <form id="myForm" class="form-horizontal">

Why does adding "return false;" to a submit event prevent the page from refreshing?

I'm doing a tutorial on making a chat server with Node.js and socket.io. Here's what I had in the html:
<form id='chat_form'>
<input id='chat_input' />
<button>Send</button>
</form>
<script type='text/javascript'>
var socket = io();
$('#chat_form').submit(function(){
var message = $('#chat_input').val();
socket.emit('messages', message);
$('#chat_input').val('');
});
</script>
I won't bother putting what I had on the back-end, because that part all worked fine. But in the browser, every time I submitted, the page refreshed, and a /? was added to the end of the URL bar.
Looked around for a bit, and found another tutorial (the one on the socket.io website), that had basically the same code, but they had added return false; to the end of their submit event. Tried that out and it worked fine. I'd like to understand why that worked though. Can anyone explain? Also, can you explain why the /? was added to the URL?
first about /?:
default method of form submit is GET, but you can change it with <form method="POST"> (while default is <form method="GET"> if method is not provided).
With POST form data is passed in request body, with GET - in request url params.
If you have
<form action="/submit.php" method="GET">
<input name="foo" value="1" />
<input name="bar" value="2" />
</form>
And you submit that form, you'll get URL something like /submit.php?foo=1&bar=2.
In your case you have no inputs with name attribute, so your GET params are "empty" (/?<params should go there>).
You can read more in:
http://www.w3schools.com/tags/att_form_method.asp
About return false;
Submitting form forces page reload (with either POST or GET request). If you submit form with javascript you need to prevent this default action. You can do this by
$('#chat_form').submit(function(event){
//....
event.preventDefault();
});
Or with return false;.
A simple and easy answer to this is
"Return false prevents navigation"
Return false is always used in those case where user or browser action needs to be stooped.
In every programming language, the code after return is not executed, which means further action wont take place, which can be
stopping form submit
stopping navigation and hyperlink jumps
other than return false you can also use
event.preventDefault();
event.stopPropagation();
Now about form submit
A form is submitted using GET and POST which can be decided by the author using method="POST" attribute in form tag,
when nothing given by default form submits using GET
which passes values in url - ex - something.html?para1=value1&para2=value2
which is fast and less secure, every time you submit a form with get all the form elements will be passed in the url
From the jQuery submit() docs:
Now when the form is submitted, the message is alerted. This happens prior to the actual submission, so we can cancel the submit action by calling .preventDefault() on the event object or by returning false from our handler.
So what's happening is, the default event behavior triggered when the submit event occurs is being prevented.
The default method of submission for the jQuery submit function is an HTML GET, which supplies the form paramaters as a URL query, in the form of /?queryParam=value. Hence, the /? appears in the URL, with no query parameters after the /? (as none are being supplied in the form).
Hope this helps!
Return false prevents all of the default functions of html element events from firing.
An example is an html form, once you hit the submit button it's default is to navigate to another page. You don't want that to happen if you are using Ajax functions to send data to a server without leaving the page.
Another way to do it is pass an event object parameter to the event function.
Then at the beginning of the function type event.preventDefault ();.
The following link offers a good explanation of why the trailing slash is present. https://webmasters.stackexchange.com/questions/35643/is-trailing-slash-automagically-added-on-click-of-home-page-url-in-browser
your form element has no method, so default method get is set. If you click send all the input elements are added after current page+?
return false prevents the submit action to perform.

Yii and Javascript form submission

I'm using Yii as a PHP framework for my site. Additionally, my site uses some js/jquery like, say, a jQuery UI Dialog widget (except for those dialogs, the rest of the code is pure normal html form components and jQuery code for the event handlers).
In the Yii side, I use CForms to build my forms from specifications file.
When I test if the form was submitted, I must do it for a certain button. This is not only forced, but I also take advantage of it.
if ($myCFormInstance->submitted('approve')) {
//process approval code
} else if ($myCFormInstance->submitted('reject')) {
//process rejection code
}
The actual problem I have is a bit conceptual one, since -fortunately- I know what's going on with my code and -again, fortunately- know the problem root:
Somewhere in My code I intercept the submit button's click event:
$(function(){
$(".critical-action").click(function(e){
var form = $(this).closest("form");
e.preventDefault();
e.stopImmediatePropagation();
confirmDialog("¿Continuar?", "#critical-action-dialog", function(){
form.submit();
});
});
});
Say the .critical-action classed elements are always a submit button in a form.
The intention of the code: cancel the form submission, and perform it only if the user -in the dialog- clicks the "Yes, Continue" (i.e. confirming the action) button.
This code works as expected, and have no problems at a javascript level BUT -and here goes my issue- when doing form.submit(), the button is not sent as part of the form. This is obvious: I'm sending the form without specifying any button. In the case of Approve and Reject, which have two buttons, the example explains itself: if the form.submit() call could send their buttons ¿which of them should send?.
Question: So, since form.submit() doesn't send any button, but I actually need buttons ¿how can I send the form "with the corresponding button" -i.e. a button I choose to specify, which should correspond to this in the click handler function context- automatically via javascript? The button NEEDS to be identified by Yii in order to process the form (specially with the Approve and Reject case).
If you added a hidden input to the form, you can modify the input value with jQuery before you submit the form, like this:
$("#inputID").val('approve');
If you want to set the value to the value of the clicked button via $(this).val(), be aware of the issue that could result in an IE browser, explain here. The second answer (by postpostmodern) has a solution to this issue.

Why the 'return false;' statement in a submit event?

I'm wondering what exactly the return false; statement is doing in this Pig-Latin translator? By removing it I can see that the page seems to automatically refresh (immediately wipes the 'translation' rather than leaving it), but I don't understand the mechanics. What's going on? I don't think the HTML or JS is necessary to answer my question, but let me know if I'm mistaken and I'll paste it in.
The jQuery:
$(function() {
$("form#translator").submit(function() {
var englishWord = $("input#word").val();
var translatedWord = englishToPigLatin(englishWord);
$("#english").append(englishWord);
$("#pig-latin").append(translatedWord);
$("#translation").show();
return false;
});
});
From the .submit documentation (emphasis mine):
Now when the form is submitted, the message is alerted. This happens prior to the actual submission, so we can cancel the submit action by calling .preventDefault() on the event object or by returning false from our handler.
So, returning false prevents the default action (in this case form submission), but also stops the propagation of the event. It is equivalent to calling event.preventDefault() and event.stopPropagation().
It seems like you don't know how forms are processed:
When a form is submitted, the browser is making a GET or POST request to the URL provided in the form's action attribute, i.e. it will load that URL. If no action attribute is provided, the request will be made to the current URL, which effectively reloads the page (but also sends data to the server).
For more information, see MDN - Sending and retrieving form data.
You are executing the function when the form is being submitted. By returning false you effectively disable the form submit so the page won't get submitted to the server.
If you do submit to the server, the server will return the new page and your javascript changes will indeed be gone until you click the submit button again.
Alternatively you could use this instead of return false:
your_form.submit(function(e){
e.preventDefault();
});
Versus:
your_form.submit(function(e){
return false;
});
by "return false" , the form will executed, but your page will not reload / refresh.

Categories