TL;DR; 显示消息并稍后重定向可提供文档位置以保留在当前页面上。您不能提交表单并停留在当前页面上。使用 AJAX 又名 XMLHttpRequest 将数据发送到您的服务器,而不是提交表单。
HTMLFormElement 的工作原理
在早期,在 JavaScript 出现之前,表单元素旨在将数据从页面发送到服务器,然后服务器处理数据,并以浏览器加载的新页面进行响应。即使在今天,浏览器在提交表单时也是如此。
The standard 说:
“表单元素代表一个超链接,可以通过与表单相关的元素的集合进行操作”(强调我的)。
现在,如果您不想更深入地解释表单提交的工作原理,可以向下滚动到所有这些在实践中一章。
但是当点击提交按钮时实际发生了什么?该标准定义了一个Form submission algorithm,非常详细,有点难以理解。以下是高度简化的主要步骤:
- 浏览器检查是否可以提交表单(文档完全活动、沙盒等),检查失败会取消算法
- 执行由元素属性定义的验证,如果验证失败则取消算法
- 附加的提交事件被执行(附加的事件只能触发一次)
- 如果任何名为
event.preventDefault 的提交处理程序被取消,算法将被取消
- 设置了表单的编码
- 创建表单控制元素的条目列表
- 解析表单的各种属性(例如,如果未设置属性,则操作默认为表单文档的 URL)
- 根据表单的
method属性(默认为GET)和action属性中的URL架构选择内部发送方式
-
导航计划被创建(任务)并发送到事件队列(包括变形的表单数据)
- 执行事件队列中的任务,并导航到新页面
在真正的算法中,会执行更多的动作,例如。步骤 1 在许多步骤之间执行,该算法防止并发提交。此外,所描述的算法仅在激活实际提交按钮时有效。如果通过form.submit方法通过JS调用提交,则跳过第2-4步。
上述算法解释了提交事件处理程序中的 JS 表单验证如何取消提交(#4)。 unload document process (#10) 解释了当新页面即将加载时超时是如何中断的(所有未决的计时器和事件都被中止)。但是,这些都没有真正解释,提交事件处理程序中的location.href 如何在没有超时的情况下被忽略。
这个问题的答案隐藏在表单的定义中:它“代表一个超链接”。激活超链接会设置特定的内部navigation 事件和内部“持续导航”标志。任何超链接的任何后续激活都会检查该标志,如果设置了该标志,则会取消该标志。设置location.href 实际上不是超链接,而是it uses 相同的导航机制,因此如果检测到待处理的导航,它也会被取消。值得注意的是,在 submit 处理程序中调用event.preventDefault 会立即取消设置“ongoing-navigation”标志,这使得设置location.href 稍后在处理程序中再次起作用。
(上面描述的导航行为被大大简化了。为了完全理解浏览器导航的工作原理,提供标准中描述的事件队列和导航过程的深入知识(细节甚至部分依赖于实现) ),但这些不是此答案的目标。)
所有这些都在实践中
由于前面描述的 HTML 表单的大部分操作都是在后台执行的,从程序员的角度来看,这一切如何应用?让我们做一个综合(没有具体的理由)。
-
<form> 是一个链接,它可以向服务器发送比<a> 等常规链接更多的信息
-
表单的
action 属性大致等于常规链接的href 属性
- 提交表单总是会加载一个新页面,这是提交的默认操作
- 在提交事件未决时,阻止任何导航到另一个页面的尝试(包括单击链接或提交其他表单,或在 JS 中设置
location.href)
- 程序员可以通过为 form 设置 submit 事件监听器来干扰表单提交
- 如果基于属性的表单验证失败,则不会执行附加的提交事件处理程序
- 提交事件处理程序可以修改当前页面和要提交的数据,也可以取消表单提交
- 尽管在提交事件处理程序中做了什么,在处理程序执行完成时提交表单,除非处理程序取消提交事件的默认操作
那么是什么触发了表单提交呢?有多种方法可以开始表单提交。以下所有元素都是所谓的提交者元素,它们的默认操作是“提交表单”:
<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"> 上点击 ENTER
- 在JS中调用
form.submit方法
注意无类型的<button> 元素,默认情况下它也是一个提交按钮,放置在表单中时。
防止事件的默认动作
有些事件有一个默认动作,在事件处理函数完成后执行。例如submit事件的默认动作是提交表单触发事件。您可以通过调用事件对象的preventDefault 方法来阻止在事件处理程序中执行默认操作:
event.preventDefault();
event 是在处理程序的参数中传递的对象,或者是全局 event 对象。
从事件处理函数返回false 不会阻止表单提交。这仅适用于内联侦听器,当return false; 写入onsubmit 属性时。
上面提交者元素列表中的前三个元素比其他元素“更强”。如果您已将 click 侦听器附加到提交/图像的输入类型或提交的按钮类型,阻止 (click) 事件的默认操作不会阻止表单提交。其他元素也可以在 click 侦听器中执行此操作。为了防止在所有情况下提交,您必须在 表单上 收听 submit 事件,而不是点击提交者元素。
再说一遍:form.submit 方法在任何脚本中调用,将跳过所有表单验证并且不会触发任何提交事件,它不能通过 JS 取消。值得注意的是,这仅代表本机 submit 方法,例如。 jQuery 的 .submit 不是原生的,它调用附加的提交处理程序。
发送数据并停留在当前页面
只有不提交表单才能发送数据并仍然停留在当前页面上。有多种方法可以防止提交。最简单的方法是根本不在标记中包含表单,但这会在 JS 中进行大量额外的数据处理工作。最好的方法是防止提交事件的默认动作,任何其他的表单方式都会导致额外的工作,例如,在没有提交者元素的情况下触发 JS 仍然提供检测提交者元素列表中 #5 所做的提交。
由于数据不提交就不会到服务器,所以需要用JS发送数据。使用的技术称为 AJAX(异步 Javascript 和 Xml)。
这是一个简单的普通 JS 代码示例,用于向服务器发送数据并停留在当前页面上。该代码使用XMLHttpRequest object。通常,代码放置在表单的提交事件处理程序中,如下例所示。您可以在页面上的脚本中的任何位置发出请求,如果不涉及提交事件,则不需要阻止默认操作。
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);
});
表格的类比散布在整个代码中:
- 控制元素值包含在
data 中(form 是对现有表单元素的引用)
-
method 属性是xhr.open 的第一个参数
-
action 属性是xhr.open 的第二个参数
-
enctype 属性由 FormData 构造函数创建(默认为“multipart/form-data”)
AJAX 和表单提交的区别在于,当从服务器获取 AJAX 调用的响应时,JavaScript 会在 xhr 的 load 处理程序中继续执行,而不是在提交表单时浏览器加载新页面.在那个load 处理程序中,您可以对作为响应发送的数据服务器执行您需要的任何操作,或者只是重定向(也可以通过超时成功)。当您不需要请求成功/失败或任何响应数据的通知时,也可以将负载处理程序排除在代码之外。
在现代浏览器中,您还可以使用Fetch API 发出 AJAX 请求。此外,大多数常用框架(如果不是全部)和许多库(如 jQuery)都有自己的(或多或少简化的)各种基于 AJAX 的任务的实现。
备注
由于内部提交算法中执行的任务的顺序,验证属性在调用提交事件处理程序之前处理。如果任何验证失败,算法将被取消。这意味着,当表单验证失败时,提交事件处理程序也不会执行。
基于验证属性的验证仅在激活提交者元素后执行,验证属性不影响FormData构造函数创建的对象,该构造函数可以随时创建,不一定需要提交事件。
创建FormData 对象时忽略表单的target 属性。 AJAX 调用总是针对当前页面,它不能被重定向到另一个文档。
发送FormData对象不是必须的,你可以发送任何你需要的数据,但是必须按照请求的method进行格式化。