了解HTML表单元素的行为 [英] Understanding HTML Form Element behavior

查看:89
本文介绍了了解HTML表单元素的行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个很少有文本字段和提交按钮的表单.提交处理程序附加到表单,以便在提交之前验证表单.此外,处理程序应该向用户显示OK消息,最后重定向到首页.

验证工作正常,但验证成功后,仅短暂显示或根本不显示OK消息,并且刷新页面而不是重定向页面.

这是表格:

<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>

和相关的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);
});

我也尝试了不超时的重定向,但是结果是一样的,页面仅刷新,它从未导航到首页.我该怎么做才能发布表单并仍然能够看到消息并进行重定向?

解决方案

TL; DR; 显示消息并在以后重定向可将文档位置保留在当前页面上.您无法提交表单并停留在当前页面上.使用AJAX aka XMLHttpRequest将数据发送到您的服务器,而不是提交表单.

HTMLFormElement的工作原理

早期,在JavaScript出现之前,表单元素用于将数据从页面发送到服务器,然后服务器处理数据,并响应一个新页面,该页面由浏览器加载.即使在今天,提交表单时,浏览器的操作也完全相同.

标准说:

" 表单元素表示超链接,可以通过与表单相关的元素的集合来操纵该超链接". (强调我的).

现在,如果您不希望深入了解表单提交的工作原理,则可以滚动至实践中的所有内容一章.

但是,当单击提交"按钮时,实际发生了什么?该标准定义了表单提交算法,它非常详细,很难遵循.以下是大大简化的主要步骤:

  1. 浏览器检查是否可以提交表单(文档处于完全活动状态,沙箱等),否则检查将取消算法
  2. 执行由元素属性定义的验证,如果验证失败,则取消算法
  3. 附加的提交事件已执行(附加事件只能触发一次)
  4. 如果有任何称为event.preventDefault的提交处理程序,则该算法将被取消
  5. 已设置表单的编码
  6. 创建表单控件元素的条目列表
  7. 解析表单的各种属性(例如,如果未设置属性,则动作默认为表单文档的URL)
  8. 根据表单的method属性(默认为GET)和action属性中URL的架构选择内部发送方法
  9. 创建一个导航计划(一个任务)并将其发送到事件队列(包括变形的表单数据)
  10. 执行事件队列中的任务,并导航到新页面

在实际算法中,例如,执行了更多的动作.步骤1在许多步骤之间执行,该算法可防止同时提交.同样,所描述的算法仅在激活实际提交按钮时才有效.如果通过form.submit方法通过JS调用提交,则步骤2-4将跳过.

以上算法说明了提交事件处理程序中的JS表单验证如何取消提交(#4). 卸载文档过程 (#10)解释了如何在要加载新页面时暂停超时(所有挂起的计时器和事件均被中止).但是,所有这些实际上都无法解释,如何在没有超时的情况下忽略提交事件处理程序中的location.href.

该问题的答案隐藏在以下形式的定义中:"代表超链接".激活超链接会设置一个特定的内部导航事件,以及内部的进行中的导航"旗帜.以后任何超链接的激活都会检查该标志,如果设置了该标志,则取消该标志.设置location.href实际上不是超链接,但它使用相同的导航机制,因此,如果检测到挂起的导航,它也将被取消.值得注意的是,在 submit 处理程序中调用event.preventDefault会取消设置正在进行的导航",立即标记,这会使设置location.href稍后在处理程序中再次起作用.

(上述导航行为已大大简化.要完全了解浏览器导航的工作原理,请提供标准中描述的事件队列和导航过程的深入知识(详细信息甚至部分依赖于实现) ),但这不是此答案的目的.)

所有这一切在实践中

由于大多数前面描述的HTML Form动作都是在后台执行的,从程序员的角度来看,如何将其全部应用?让我们进行综合(没有特定的原理).

  • <form>是一个链接,与像<a>
  • 这样的常规链接相比,它可以向服务器发送更多信息. 表单的
  • action属性与常规链接的href属性大致相等
  • 提交表单始终会加载一个新页面,这是提交操作的默认操作
  • 在提交事件未决期间,任何尝试导航到另一个页面的尝试(包括单击链接或提交其他表单,或在JS中设置location.href)都被阻止
  • 程序员可以通过将 submit 事件监听器设置为 form
  • 来干扰表单提交
  • 如果基于属性的表单验证失败,则不会执行附加的提交事件处理程序
  • 提交事件处理程序可以修改当前页面和要提交的数据,还可以取消表单提交
  • 尽管在提交事件处理程序中会执行什么操作,在处理程序执行完成后才提交表单,除非处理程序取消了Submit事件的默认操作

那么什么触发表单提交呢?有多种方法可以启动表单提交.以下所有元素都称为提交者元素,即具有提交表格"形式的元素.作为其默认操作:

  1. <input type="submit" value="Submit">
  2. <input type="image" alt="Submit">
  3. <button type="submit">Submit</button>
  4. <button>Submit</button>
  5. 在活动<input type="date/number/password/search/tel/text/time/url/week">
  6. 上击中 ENTER
  7. 在JS中调用form.submit方法

请注意无类型的<button>元素,当放置在表单中时,默认情况下它也是一个提交按钮.

防止事件的默认操作

某些事件具有默认动作,该动作在事件处理函数完成后执行.例如,submit事件的默认操作是提交触发事件的表单.您可以通过调用事件对象的preventDefault方法来阻止在事件处理程序中执行默认操作:

event.preventDefault();

event是在处理程序的参数中传递的对象,或者是全局event对象.

事件处理程序函数返回false不会阻止表单提交.当在onsubmit属性中写入return false;时,此方法仅适用于内联侦听器.

上面的提交者元素列表中的前三个元素为更强",即更强".比其他元素.如果您已将 click 侦听器附加到提交/图像的输入类型或按钮的提交类型,则阻止(click)事件的默认操作不会阻止表单提交.其他元素也可以在click侦听器中执行此操作.为了在所有情况下都阻止提交,您必须监听表单上的submit事件 ,而不是点击提交者元素.

再次:在任何脚本中调用的form.submit方法,将跳过所有表单验证,并且不会触发任何提交事件,无法通过JS取消.值得注意的是,这仅表示本机submit方法,例如. jQuery的.submit不是本机的,它调用附加的提交处理程序.

发送数据并停留在当前页面上

要发送数据并仍然停留在当前页面上,只能通过不提交表单来实现.有多种防止提交的方法.最简单的方法是根本不在标记中包含表单,尽管如此,这在JS中处理数据时需要做很多额外的工作.最好的方法是防止Submit事件的默认操作,使用表单的任何其他方法都会导致额外的工作,例如,在没有Submitter元素的情况下触发JS仍可以检测到Submitter元素列表中的#5提交的提交.

由于不提交数据就不会将数据发送到服务器,因此您需要使用JS发送数据.使用的技术称为AJAX(异步Javascript和Xml).

这是一个简单的原始JS代码示例,该示例将数据发送到服务器并停留在当前页面上.该代码利用了 XMLHttpRequest对象.通常,将代码放置在表单的Submit事件处理程序中,如下面的示例所示.您可以在页面上的脚本中的任何位置发出请求,如果不涉及Submit事件,则无需采取默认操作.

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);
});

类似于表格的形式散布在整个代码中:

AJAX和表单提交之间的区别在于,当从服务器获取对AJAX调用的响应时,JavaScript执行在xhrload处理程序中继续进行,而不是浏览器在提交表单时加载新页面.在该load处理程序中,您可以使用作为响应发送的数据服务器来执行所需的任何操作,或者仅重定向(也可以成功超时).当您不需要通知请求成功/失败或任何响应数据时,也可以将负载处理程序置于代码之外.

在现代浏览器中,您还可以使用获取API 发出AJAX请求.而且,大多数常用框架(如果不是全部)和许多库(如jQuery)对于各种基于AJAX的任务都有自己的(或多或少简化的)实现.

备注

由于内部提交算法中执行的任务的顺序,在调用提交事件处理程序之前先处理验证属性.如果任何验证失败,则取消该算法.这意味着,当表单验证失败时,也不执行提交事件处理程序.

仅在激活Submitter元素之后才执行基于验证属性的验证,验证属性不会影响由FormData构造函数创建的对象,该对象可以随时创建,不一定需要提交事件. /p>

创建FormData对象时,将忽略表单的target属性. AJAX调用始终以当前页面为目标,无法将其重定向到另一个文档.

发送FormData对象不是强制性的,您可以发送所需的任何数据,但是必须根据请求的method对其进行格式化.

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:

  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.

这篇关于了解HTML表单元素的行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆