了解 HTML 表单元素行为

IT技术 javascript html forms
2021-02-01 16:22:37

我有一个带有几个文本字段和一个提交按钮的表单。提交处理程序附加到表单上,以便在提交前验证表单。此外,处理程序应该向用户显示 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);
});

我也试过在没有超时的情况下重定向,但结果是一样的,页面只是刷新,它永远不会导航到首页。我该怎么做才能发布我的表单并仍然能够看到消息并进行重定向?

1个回答

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

HTMLFormElement 的工作原理

在早期,在 JavaScript 出现之前,表单元素的目的是将数据从页面发送到服务器,然后服务器处理数据,并以浏览器加载的新页面作为响应。即使在今天,浏览器在提交表单时也会做同样的事情。

标准说:

表单元素代表一个超链接,可以通过一组与表单相关的元素进行操作”(我的重点)。

现在,如果您对更深入地解释表单提交的工作原理不感兴趣,您可以向下滚动到All this in practice一章。

但是当点击提交按钮时实际发生了什么?该标准定义了一个表单提交算法,非常详细,有点难以理解。以下是高度简化的主要步骤:

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

在真正的算法中,执行了更多的动作,例如。步骤 1 在许多步骤之间执行,并且该算法防止并发提交。此外,所描述的算法仅在实际提交按钮被激活时才有效。如果是通过JSform.submit方法调用提交,则跳过步骤2-4。

上述算法解释了提交事件处理程序中的 JS 表单验证如何取消提交(#4)。卸载文件处理(#10)解释了如何将超时当一个新的一页即将负载(所有挂起的计时器和事件都将立即中止)打破。但是,这些中的任何一个实际上都没有解释,如何location.href在没有超时的情况下忽略提交事件处理程序。

这个问题的答案隐藏在表单的定义中:它“代表一个超链接”。激活超链接会设置特定的内部导航事件和内部“正在进行的导航”标志。任何超链接的任何后续激活都会检查该标志,如果设置了该标志,则会被取消。设置location.href实际上不是超链接,但它使用相同的导航机制,因此如果检测到待处理的导航,它也会被取消。值得注意的是,event.preventDefault提交处理程序中调用会立即取消设置“正在进行的导航”标志,这使得location.href稍后在处理程序中再次工作。

(上面描述的导航行为被大大简化。为了完全理解浏览器导航的工作原理,提供了事件队列和导航过程的深入知识,这些知识在标准中进行了描述(细节甚至部分依赖于实现),但这些不是这个答案的目标。)

这一切在实践中

由于前面描述的 HTML 表单的大部分操作都是在后台执行的,从程序员的角度来看,这一切如何应用?让我们做一个综合(没有具体的理由)。

  • <form> 是一个链接,它可以向服务器发送比常规链接更多的信息,例如 <a>
  • action表单的属性大致等于href常规链接的属性
  • 提交表单总是加载一个新页面,这是提交的默认动作
  • location.href当提交事件未决时,将阻止任何导航到另一个页面的尝试(包括单击链接或提交其他表单,或在 JS 中设置
  • 程序员可以通过设置干扰形式提交提交事件侦听器的形式
  • 如果基于属性的表单验证失败,则不会执行附加的提交事件处理程序
  • submit 事件处理程序可以修改当前页面和要提交的数据,也可以取消表单提交
  • 然而,在提交事件处理程序中所做的事情,当处理程序执行完成时提交表单,除非处理程序取消提交事件的默认操作

那么是什么触发了表单提交呢?有多种方法可以开始提交表单。下面的所有元素都是所谓的提交者元素,它们的默认操作是“提交表单”:

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

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

防止事件的默认操作

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

event.preventDefault();

event要么是传入处理程序参数的event对象,要么是全局对象。

false事件处理函数返回不会阻止表单提交。这仅适用于内联侦听器,当return false;写入onsubmit属性时。

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

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

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

只有不提交表单才能发送数据并仍然停留在当前页面上。有多种方法可以防止提交。最简单的方法是在标记中根本不包含表单,但是这会在 JS 中进行大量的数据处理工作。最好的方法是防止提交事件的默认操作,任何其他形式的表单都会导致额外的工作,例如,在没有提交者元素的情况下触发 JS 仍然提供检测提交者元素列表中#5 所做的提交。

由于数据不提交就不会去服务器,所以需要用JS发送数据。要使用的技术称为 AJAX(Asynchronous Javascript And Xml)。

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

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

对表单的类比散布在整个代码中:

  • 控制元素值包含在dataform作为对现有表单元素的引用)中
  • method 属性是第一个参数 xhr.open
  • action 属性是第二个参数 xhr.open
  • enctype属性由FormData构造函数创建(默认为“multipart/form-data”)

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

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

评论

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

只有在激活提交者元素后才执行基于验证属性的验证,验证属性不会影响FormData构造函数创建的对象,该对象可以随时创建,不一定需要提交事件。

target创建FormData对象时会忽略表单属性AJAX 调用始终以当前页面为目标,不能重定向到另一个文档。

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