Javascript.info-表单控件

表单 <form> 和控件的特殊属性和事件:<input>,<select> 及其他。

表单 <form> 和控件的特殊属性和事件:<input><select> 及其他。

表单属性和方法

表单(form)以及例如 <input> 的控件(control)元素有许多特殊的属性和事件。

当我们学习了这些相关内容后,处理表单会变得更加方便。

导航:表单和元素

文档中的表单是特殊集合 document.forms 的成员。

这就是所谓的“命名的集合”:既是被命名了的,也是有序的。我们既可以使用名字,也可以使用在文档中的编号来获取表单。

javascript
document.forms.my; // name="my" 的表单
document.forms[0]; // 文档中的第一个表单

当我们有了一个表单时,其中的任何元素都可以通过命名的集合 form.elements 来获取到。

例如:

html
<form name="my">
  <input name="one" value="1">
  <input name="two" value="2">
</form>

<script>
  // 获取表单
  let form = document.forms.my; // <form name="my"> 元素
  // 获取表单中的元素
  let elem = form.elements.one; // <input name="one"> 元素
  alert(elem.value); // 1
</script>

可能会有多个名字相同的元素,这种情况经常在处理单选按钮中出现。

在这种情况下,form.elements[name] 将会是一个集合。例如:

html
<form>
  <input type="radio" name="age" value="10">
  <input type="radio" name="age" value="20">
</form>

<script>
let form = document.forms[0];
let ageElems = form.elements.age;
alert(ageElems[0]); // [object HTMLInputElement]
</script>

这些导航(navigation)属性并不依赖于标签的结构。所有的控件元素,无论它们在表单中有多,都可以通过 form.elements 获取到。

Fieldset 作为“子表单”

一个表单内会有一个或多个 <fieldset> 元素。它们也具有 elements 属性,该属性列出了 <fieldset> 中的表单控件。

例如:

html
<body>
  <form id="form">
    <fieldset name="userFields">
      <legend>info</legend>
      <input name="login" type="text">
    </fieldset>
  </form>

  <script>
    alert(form.elements.login); // <input name="login">
    let fieldset = form.elements.userFields;
    alert(fieldset); // HTMLFieldSetElement
    // 我们可以通过 name 从表单和 fieldset 中获取 input
    alert(fieldset.elements.login == form.elements.login); // true
  </script>
</body>

更简短的表示方式:form.name

还有一个更简短的表示方式:我们可以通过 form[index/name] 来访问元素。

换句话说,我们可以将 form.elements.login 写成 form.login

这也有效,但是会有一个小问题:如果我们访问一个元素,然后修改它的 name,之后它仍然可以被通过旧的 name 访问到(当然也能通过新的 name 访问)。

我们可以很直观地通过一个例子看到这个情况:

html
<form id="form">
  <input name="login">
</form>

<script>
  alert(form.elements.login == form.login); // true,与 <input> 相同
  form.login.name = "username"; // 修改 input 的 name
  // form.elements 更新了 name:
  alert(form.elements.login); // undefined
  alert(form.elements.username); // input
  // form 允许我们使用两个名字:新的名字和旧的名字
  alert(form.username == form.login); // true
</script>

这通常来说并不是一个问题,因为我们很少修改表单元素的名字。

反向引用:element.form

对于任何元素,其对应的表单都可以通过 element.form 访问到。因此,表单引用了所有元素,元素也引用了表单。

这是一张示意图:

例如:

html
<form id="form">
  <input type="text" name="login">
</form>

<script>
  // form -> element
  let login = form.login;
  // element -> form
  alert(login.form); // HTMLFormElement
</script>

表单元素

让我们来谈谈表单控件。

input 和 textarea

我们可以通过 input.value(字符串)或 input.checked(布尔值)来访问复选框(checkbox)和单选按钮(radio button)中的 value

像这样:

javascript
input.value = "New value";
textarea.value = "New text";
input.checked = true; // 对于复选框(checkbox)或单选按钮(radio button)

使用 textarea.value 而不是 textarea.innerHTML

请注意,即使 <textarea>...</textarea> 将它们的 value 作为嵌套的 HTML 标签来保存,我们也绝不应该使用 textarea.innerHTML 来访问它。

它仅存储最初在页面上的 HTML,而不是存储的当前 value

select 和 option

一个 <select> 元素有 3 个重要的属性:

  1. select.options —— <option> 的子元素的集合,
  2. select.value —— 当前所选择的 <option>value
  3. select.selectedIndex —— 当前所选择的 <option>编号

它们提供了三种为 <select> 设置 value 的不同方式:

  1. 找到对应的 <option> 元素(例如在 select.options 中),并将其 option.selected 设置为 true
  2. 如果我们知道新的值:将 select.value 设置为对应的新的值。
  3. 如果我们知道新的选项的索引:将 select.selectedIndex 设置为对应 <option> 的编号。

下面是这三种方式的示例:

html
<select id="select">
  <option value="apple">Apple</option>
  <option value="pear">Pear</option>
  <option value="banana">Banana</option>
</select>

<script>
  // 下面这三行做的都是同一件事
  select.options[2].selected = true;
  select.selectedIndex = 2;
  select.value = 'banana';
  // 请注意:选项编号是从零开始的,所以编号 2 表示的是第三项
</script>

和大多数其它控件不同,如果 <select> 具有 multiple 特性(attribute),则允许多选。尽管这个特性(attribute)很少被用到。

对于多选的值,使用第一种设置值的方式:在 <option> 子元素中添加/移除 selected 属性。

这是一个如何从多选的 <select> 中获取选择的值的示例:

html
<select id="select" multiple>
  <option value="blues" selected>Blues</option>
  <option value="rock" selected>Rock</option>
  <option value="classic">Classic</option>
</select>
<script>
  // 从 multi-select 中获取所有选定的 `value`
  let selected = Array.from(select.options)
    .filter(option => option.selected)
    .map(option => option.value);
  alert(selected); // blues,rock
</script>

<select> 元素的完整规范可以在规范 https://html.spec.whatwg.org/multipage/forms.html#the-select-element 中找到。

image-20251122233411826

可以使用ctrl单击设置多选

new Option

规范 中,有一个很好的简短语法可以创建一个 <option> 元素:

javascript
option = new Option(text, value, defaultSelected, selected);

或者我们也可以使用 document.createElement('option') 并手动设置特性(attribute)。不过,这种语法写起来可能会更短,其参数如下:

  • text —— <option> 中的文本,
  • value —— <option>value
  • defaultSelected —— 如果为 true,那么 selected HTML-特性(attribute)就会被创建,
  • selected —— 如果为 true,那么这个 <option> 就会被选中。

defaultSelectedselected 的区别是,defaultSelected 设置的是 HTML-特性(attribute),我们可以使用 option.getAttribute('selected') 来获得。而 selected 设置的是选项是否被选中。

defaultSelected是dom定义属性<option selected></option>

selected是对象属性

在实际使用中,通常应该将同时将这两个值设置为 truefalse。(或者,直接省略它们;两者都默认为 false。)

例如,下面是一个新的未被选中的选项:

javascript
let option = new Option("Text", "value");
// 创建 <option value="value">Text</option>

相同的选项,但被选中了:

javascript
let option = new Option("Text", "value", true, true);

<option> 元素具有以下属性:

  • option.selected

    <option> 是否被选择。

  • option.index

    <option> 在其所属的 <select> 中的编号。

  • option.text

    <option> 的文本内容(可以被访问者看到)。

聚焦:focus/blur

当用户点击某个元素或使用键盘上的 Tab 键选中时,该元素将会获得聚焦(focus)。还有一个 HTML 特性(attribute)autofocus 可以让焦点在网页加载时默认落在一个元素上,此外还有其它途径可以获得焦点。

聚焦到一个元素通常意味着:“准备在此处接受数据”,所以,这正是我们可以运行代码以初始化所需功能的时刻。

失去焦点的时刻(“blur”)可能更为重要。它可能发生在用户点击页面的其它地方,或者按下 Tab 键跳转到下一个表单字段,亦或是其它途径的时候。

失去焦点通常意味着:“数据已经输入完成”,所以我们可以运行代码来检查它,甚至可以将其保存到服务器上,或进行其他操作。

当处理焦点事件时,有一些重要的特性。我们将尽力把这些内容介绍完整。

focus/blur 事件

当元素聚焦时,会触发 focus 事件,当元素失去焦点时,会触发 blur 事件。

让我们使用它们来校验一个 input 字段。

在下面这个示例中:

  • blur 事件处理程序检查这个字段是否输入了电子邮箱,如果没有输入,则显示一个 error。
  • focus 事件处理程序隐藏 error 信息(在 blur 事件处理程序上会被再检查一遍):
html
<style>
  .invalid { border-color: red; }
  #error { color: red }
</style>
Your email please: <input type="email" id="input">
<div id="error"></div>
<script>
input.onblur = function() {
  if (!input.value.includes('@')) { // not email
    input.classList.add('invalid');
    error.innerHTML = 'Please enter a correct email.'
  }
};
input.onfocus = function() {
  if (this.classList.contains('invalid')) {
    // 移除 "error" 指示,因为用户想要重新输入一些内容
    this.classList.remove('invalid');
    error.innerHTML = "";
  }
};
</script>

现代 HTML 允许我们使用 input 特性(attribute)进行许多验证:requiredpattern 等。

focus/blur 方法

elem.focus()elem.blur() 方法可以设置和移除元素上的焦点。

例如,如果输入值无效,我们可以让焦点无法离开这个 input 字段:

html
<style>
  .error {
    background: red;
  }
</style>

Your email please: <input type="email" id="input">
<input type="text" style="width:220px" placeholder="make email invalid and try to focus here">

<script>
  input.onblur = function() {
    if (!this.value.includes('@')) { // not email
      // 显示 error
      this.classList.add("error");
      // ...将焦点放回来
      input.focus();
    } else {
      this.classList.remove("error");
    }
  };
</script>

这段代码在除了火狐(bug)之外的浏览器上都可以正常工作。

如果我们在 input 中输入一些内容,然后尝试使用 Tab 键或点击远离 <input> 的位置,那么 onblur 事件处理程序会把焦点重新设置到这个 input 字段上。

请注意,我们无法通过在 onblur 事件处理程序中调用 event.preventDefault() 来“阻止失去焦点”,因为 onblur 事件处理程序是在元素失去焦点 之后 运行的。

JavaScript 导致的焦点丢失

很多种原因可以导致焦点丢失。

其中之一就是用户点击了其它位置。当然 JavaScript 自身也可能导致焦点丢失,例如:

  • 一个 alert 会将焦点移至自身,因此会导致元素失去焦点(触发 blur 事件),而当 alert 对话框被取消时,焦点又会重新回到原元素上(触发 focus 事件)。
  • 如果一个元素被从 DOM 中移除,那么也会导致焦点丢失。如果稍后它被重新插入到 DOM,焦点也不会回到它身上。

这些特性有时候会导致 focus/blur 处理程序发生异常 —— 在不需要它们时触发。

最好的秘诀就是在使用这些事件时小心点。如果我们想要跟踪用户导致的焦点丢失,则应该避免自己造成的焦点丢失。

允许在任何元素上聚焦:tabindex

默认情况下,很多元素不支持聚焦。

虽然这个范围因浏览器而异,但有一件事总是正确的:用户可以交互的元素:<button><input><select><a> 等,都支持 focus/blur

另一方面,为了格式化某些东西而存在的元素像 <div><span><table> —— 默认是不能被聚焦的。elem.focus() 方法不适用于它们,并且 focus/blur 事件也绝不会被触发。

使用 HTML-特性(attribute)tabindex 可以改变这种情况。

任何具有 tabindex 特性的元素,都会变成可聚焦的。该特性的 value 是当使用 Tab(或类似的东西)在元素之间进行切换时,元素的顺序号。

也就是说:如果我们有两个元素,第一个具有 tabindex="1",第二个具有 tabindex="2",然后当焦点在第一个元素的时候,按下 Tab 键,会使焦点移动到第二个元素身上。

切换顺序为:从 1 开始的具有 tabindex 的元素排在前面(按 tabindex 顺序),然后是不具有 tabindex 的元素(例如常规的 <input>)。

不具有 tabindex 的元素按文档源顺序(默认顺序)切换。

这里有两个特殊的值:

  • tabindex="0" 会使该元素被与那些不具有 tabindex 的元素放在一起。也就是说,当我们切换元素时,具有 tabindex="0" 的元素将排在那些具有 tabindex ≥ 1 的元素的后面。

    通常,它用于使元素具有焦点,但是保留默认的切换顺序。使元素成为与 <input> 一样的表单的一部分。

  • tabindex="-1" 只允许以编程的方式聚焦于元素。Tab 键会忽略这样的元素,但是 elem.focus() 有效。

举个例子,这里有一个列表。点击第一项,然后按 Tab 键:

html
点击第一项,然后按 Tab 键。跟踪顺序。请注意,多按几次 Tab 键后,会将焦点移到这个通过 iframe 嵌入的示例的外面。
<ul>
  <li tabindex="1">One</li>
  <li tabindex="0">Zero</li>
  <li tabindex="2">Two</li>
  <li tabindex="-1">Minus one</li>
</ul>

<style>
  li { cursor: pointer; }
  :focus { outline: 1px dashed green; }
</style>

顺序就像这样:1 - 2 - 0。通常,<li> 不支持聚焦,但 tabindex 可以使它能聚焦,使这成为可能,并且还带有事件以及 :focus 样式。

属性 elem.tabIndex 也有效

我们可以使用 elem.tabIndex 通过 JavaScript 来添加 tabindex。效果是一样的。

focus/blur 委托

focusblur 事件不会向上冒泡。

例如,我们不能把 onfocus 放在 <form> 上来对其进行高亮,像这样:

html
<!-- on focusing in the form -- add the class -->
<form onfocus="this.className='focused'">
  <input type="text" name="name" value="Name">
  <input type="text" name="surname" value="Surname">
</form>

<style> .focused { outline: 1px solid red; } </style>

上面这个示例并不工作,因为当用户聚焦于 <input> 时,focus 事件只会在该 <input> 上触发。它不会向上冒泡。所以 form.onfocus 永远不会触发。

这里有两个解决方案。

方案一,有一个遗留下来的有趣的特性(feature):focus/blur 不会向上冒泡,但会在捕获阶段向下传播。

这样可以生效:

html
<form id="form">
  <input type="text" name="name" value="Name">
  <input type="text" name="surname" value="Surname">
</form>
<style> .focused { outline: 1px solid red; } </style>
<script>
  // 将处理程序置于捕获阶段(最后一个参数为 true)
  form.addEventListener("focus", () => form.classList.add('focused'), true);
  form.addEventListener("blur", () => form.classList.remove('focused'), true);
</script>

方案二,可以使用 focusinfocusout 事件 —— 与 focus/blur 事件完全一样,只是它们会冒泡

值得注意的是,必须使用 elem.addEventListener 来分配它们,而不是 on<event>

所以,这是另一个可行的变体:

html
<form id="form">
  <input type="text" name="name" value="Name">
  <input type="text" name="surname" value="Surname">
</form>

<style> .focused { outline: 1px solid red; } </style>

<script>
  form.addEventListener("focusin", () => form.classList.add('focused'));
  form.addEventListener("focusout", () => form.classList.remove('focused'));
</script>

事件:change,input,cut,copy,paste

让我们介绍一下伴随数据更新的各种事件。

事件:change

当元素更改完成时,将触发 change 事件。

对于文本输入框,当其失去焦点时,就会触发 change 事件。

例如,当我们在下面的文本字段中键入内容时 —— 不会触发 change 事件。但是,当我们将焦点移到其他位置时,例如,点击按钮 —— 就会触发 change 事件:

html
<input type="text" onchange="alert(this.value)">
<input type="button" value="Button">

对于其它元素:selectinput type=checkbox/radio,会在选项更改后立即触发 change 事件。

html
<select onchange="alert(this.value)">
  <option value="">Select something</option>
  <option value="1">Option 1</option>
  <option value="2">Option 2</option>
  <option value="3">Option 3</option>
</select>

事件:input

每当用户对输入值进行修改后,就会触发 input 事件。

与键盘事件不同,只要值改变了,input 事件就会触发,即使那些不涉及键盘行为(action)的值的更改也是如此:使用鼠标粘贴,或者使用语音识别来输入文本。

例如:

html
<input type="text" id="input"> oninput: <span id="result"></span>
<script>
  input.oninput = function() {
    result.innerHTML = input.value;
  };
</script>

如果我们想要处理对 <input> 的每次更改,那么此事件是最佳选择。

另一方面,input 事件不会在那些不涉及值更改的键盘输入或其他行为上触发,例如在输入时按方向键 ⇦ ⇨。

无法阻止 oninput 中的任何事件

当输入值更改,就会触发 input 事件。

所以,我们无法使用 event.preventDefault() —— 已经太迟了,不会起任何作用了。

事件:cut,copy,paste

这些事件发生于剪切/拷贝/粘贴一个值的时候。

它们属于 ClipboardEvent 类,并提供了对剪切/拷贝/粘贴的数据的访问方法。

我们也可以使用 event.preventDefault() 来中止行为,然后什么都不会被复制/粘贴。

例如,下面的代码阻止了剪切/拷贝/粘贴的事件,并显示出了我们所尝试剪切/拷贝/粘贴的内容:

html
<input type="text" id="input">
<script>
  input.onpaste = function(event) {
    alert("paste: " + event.clipboardData.getData('text/plain'));
    event.preventDefault();
  };

  input.oncut = input.oncopy = function(event) {
    alert(event.type + '-' + document.getSelection());
    event.preventDefault();
  };
</script>

请注意,在剪切/复制事件处理程序中调用 event.clipboardData.getData(...) 只会得到一个空字符串。从技术上讲,这是因为此时数据还未存入剪切板。如果我们使用 event.preventDefault(),则它根本不会被复制。

所以上面的例子中使用 document.getSelection() 来得到被选中的文本。你可以在 选择(Selection)和范围(Range) 中了解更多关于文本选择(document selection)的细节。

我们不仅可以复制/粘贴文本,也可以复制/粘贴其他各种内容。例如,我们可以在操作系统的文件管理器中复制一个文件并进行粘贴。

这是因为 clipboardData 实现了 DataTransfer 接口,通常用于拖放和复制/粘贴。这超出了本文所讨论的范围,但你可以在 DataTransfer 规范 中进行详细了解。

另外,还有一个可以访问剪切板的异步 API:navigator.clipboard,详见 Clipboard API 和事件规范火狐浏览器(Firefox)尚未支持

安全限制

剪贴板是“全局”操作系统级别的东西。用户可能会在各种应用程序之间切换,复制/粘贴不同的内容,而浏览器页面不应该能访问这些内容。

因此,大多数浏览器仅允许在某些用户操作范围内(例如复制/粘贴等)对剪切板进行无缝的读/写访问。

除火狐(Firefox)浏览器外,所有浏览器都禁止使用 dispatchEvent 生成“自定义”剪贴板事件,即使我们设法调度此类事件。规范也明确声明了,合成(syntetic)事件不得提供对剪切板的访问权限。

此外,如果有人想将 event.clipboardData 保存在事件处理程序中,然后稍后再访问它 —— 这也不会生效。

重申,event.clipboardData 仅在用户启动的事件处理程序的上下文中生效。

另外, navigator.clipboard 是一个较新的 API,适用于任何上下文。如果需要,它会请求用户的许可

表单:事件和方法提交

提交表单时,会触发 submit 事件,它通常用于在将表单发送到服务器之前对表单进行校验,或者中止提交,并使用 JavaScript 来处理表单。

form.submit() 方法允许从 JavaScript 启动表单发送。我们可以使用此方法动态地创建表单,并将其发送到服务器。

让我们看看它们的更多细节。

事件:submit

提交表单主要有两种方式:

  1. 第一种 —— 点击 <input type="submit"><input type="image">。(默认表单里面放一个button也会提交)
  2. 第二种 —— 在 input 字段中按下 Enter 键。

这两个行为都会触发表单的 submit 事件。处理程序可以检查数据,如果有错误,就显示出来,并调用 event.preventDefault(),这样表单就不会被发送到服务器了。

在下面的表单中:

  1. 在文本字段中按下 Enter 键。
  2. 点击 <input type="submit">

这两种行为都会显示 alert,而因为代码中的 return false,表单不会被发送到别处:

html
<form onsubmit="alert('submit!');return false">
  First: Enter in the input field <input type="text" value="text"><br>
  Second: Click "submit": <input type="submit" value="Submit">
</form>

submitclick 的关系

在输入框中使用 Enter 发送表单时,会在 <input type="submit"> 上触发一次 click 事件。

这很有趣,因为实际上根本没有点击

这是一个示例:

html
<form onsubmit="return false">
 <input type="text" size="30" value="Focus here and press enter">
 <input type="submit" value="Submit" onclick="alert('click')">
</form>

方法:submit

如果要手动将表单提交到服务器,我们可以调用 form.submit()

这样就不会产生 submit 事件。这里假设如果开发人员调用 form.submit(),就意味着此脚本已经进行了所有相关处理。

有时该方法被用来手动创建和发送表单,如下所示:

javascript
let form = document.createElement('form');
form.action = 'https://google.com/search';
form.method = 'GET';

form.innerHTML = '<input name="q" value="test">';

// 该表单必须在文档中才能提交
document.body.append(form);

form.submit();

评论

评论加载中……