【有趣JS连载】HTML 表单及 localStorage 的使用 (第11章)

重新介绍 HTML 表单

网页不仅仅是用来显示数据的。有了 HTML 表单,咱们可以收集和操作用户数据。在本章中,通过构建一个简单的 HTML 表单来学习表单的相关的知识。

在这个过程中,会了解更多关于 DOM 事件的信息,从在 第8章 我们知道了一个 <form> 元素是一个 HTML 元素,它可能包含其他的子元素,比如:

  • <input> 用于捕获数据

  • <textarea> 用于捕获文本

  • <button> 用于提交表单

在本章中,咱们构建一个包含 <input><textarea>  <button> 的表彰。理想情况下,每个 input 都应该具有 type 的属性,该属性指示输入类型: 例如 textemailnumberdate 等。除了 type 属性之外,可能还希望向每个表单元素添加 id 属性。

input  textarea 也可以有一个 name 属性。如果你们想在不使用 JS 的情况下发送表单,name 属性非常重要。稍后会详细介绍。

另外,将每个表单元素与 <label> 关联也是一种常见的方式。在下面的示例中,会看到每个 label  for 属性绑定对应 input 元素的 id,作用是点击 label元素就能让 input 聚焦。

如果没有填写所有需要的信息,用户将无法提交表单。这是一个避免空数据的简单验证,从而防止用户跳过重要字段。有了这些知识,现在就可以创建 HTML 表单了。创建一个名为 form.html 的新文件并构建 HTML:

640?wx_fmt=png

如上所述,表单中的 input 具有正确的属性,从现在开始,可以通过填充一些数据来测试表单。编写 HTML 表单时,要特别注意 type 属性,因为它决定了用户能够输入什么样的数据。

HTML5 还引入了表单验证:例如,类型为 email 的输入只接受带有“at”符 号@ 的电子邮件地址。不幸的是,这是对电子邮件地址应用的惟一检查:没有人会阻止用户输入类似 a@a 这样的电子邮件。它有 @,但仍然是无效的(用于电子邮件输入的 pattern属性可以帮助解决这个问题。

 <input> 上有很多可用的属性,我发现 minlength  maxlength 是最有用的两个。在实战中,它们可以阻止懒惰的垃圾邮件发送者发送带有 “aa”  “testtest” 的表单。

640?wx_fmt=png

有了这个表单,咱们就可以更进一步了,接着,来看下表单是如何工作的。

表单是如何工作

HTML 表单是 HTMLFormElement 类型的一个元素。与几乎所有的 HTML 元素一样,它连接到 HTMLElement,后者又连接到 EventTarget。当我们访问 DOM 元素时,它们被表示为 JS 对象。在浏览器中试试这个:

const aForm = document.createElement("form");
console.log(typeof aForm);

输出是 “object”,而像 HTMLElement  EventTarget 这样的实体是函数:

console.log(typeof EventTarget); // "function"

因此,如果任何 HTML 元素都连接到 EventTarget,这意味着 <form>  EventTarget 的“实例”,如下:

const aForm = document.createElement("form");
console.log(aForm instanceof EventTarget); // true

form  EventTarget 的一种专门化类型。每个EventTarget 都可以接收和响应 DOM 事件(如第8章所示)。

DOM 事件有很多类型,比如 clickblurchange 等等。现在,咱们感兴趣的是 HTML 表单特有的 submit 事件。当用户单击 input  type 为 “submit” 的按钮(元素必须出现在表单中)时,就会分派 submit 事件,如下所示:

640?wx_fmt=png

请注意,<button type="submit">Submit</button>  就在表单内部。一些开发人员使用input 方式:

<!-- 通用提交按钮 -->
<input type="submit">

<!-- 自定义提交按钮 -->
<button>提交表单</button>

<!-- 图像按钮 -->
<input type='image' src='av.gif'/>

只要表单存在上面 列出的任何一种按钮,那么在相应表单控件拥有焦点的情况下,按回车键就可以提交表单。(textarea 是一个例外,在文本中回车会换行。)如果表单里没有提交按钮,按回车键不会提交表单。

咱们的目标是获取表单上的所有用户输入,所以,需要监听 submit 事件。

const formSelector = document.querySelector("form");

new Form(formSelector);

DOM 还提供 document.forms,这是页面内所有表单的集合。咱们现在只需要:

const formSelector = document.forms[0];

new Form(formSelector);

现在的想法是:给定一个表单选择器,我们可以注册一个事件监听器来响应表单的发送。为了注册监听器,我们可以使用构造函数,并让它调用一个名为 init 的方法。在与 form.html 相同的文件夹中创建一个名为 form.js 的新文件,并从一个简单的类开始:

640?wx_fmt=png

咱们的事件监听器是 this.handleSubmit,与每个事件监听器一样,它可以访问名为 event 的参数。从第8章中应该知道,事件是实际分派的事件,其中包含有关动作本身的许多有用信息。咱们来实现 this.handleSubmit

640?wx_fmt=png

然后,实例化类 From:

const formSelector = document.forms[0];

new Form(formSelector);

此时,在浏览器中打开 form.html。输入内容并点击“提交”。会发生什么呢? 输出如下:

http://localhost:63342/little-javascript/code/ch10/form.html?name=Valentino&description=Trip+to+Spoleto&tak=We%27re+going+to+visit+the+city%21

这是怎么回事?大多数 DOM 事件都有所谓的“默认行为”。submit 事件尤其尝试将表单数据发送到虚构的服务器。这就是在没有 JS的 情况下发送表单的方式,因为它是基于 DjangoRailsfriends 等 web 框架的应用程序的一部分。

每个输入值都映射到相应的 name 属性。在本例中不需要 name,因为这里咱们想用 JS 来控制表单,所以需要禁用默认行为。可以通过调用 preventDefault 来禁用:

640?wx_fmt=png

保存文件,然后再次刷新 form.html。尝试填写表格,然后单击提交。会看到 event对象打印到控制台:

Event {...}
    bubbles: true
    cancelBubble: false
    cancelable: true
    composed: false
    currentTarget: null
    defaultPrevented: true
    eventPhase: 0
    isTrusted: true
    path: (5) [form, body, html, document, Window]
    returnValue: false
    srcElement: form
    target: form
    timeStamp: 8320.840000000317
    type: "submit"

 event 对象的许多属性中,还有 event.target,这表明咱们的 HTML 表单与所有输入一起保存在那里,来看看是否确实如此。

从 from 提取数据

为了获取表单的值,通过检查 event.target,您将发现有一个名为 elements 的属性。该属性是表单中所有元素的集合。这个 elements 集合是一个有序列表,其中包含着表单的所有字段,例如 <input><textarea><button>  <fieldset>。如果尝试使用 console.log(event.target.elements) 进行打印,则会看到:

0: input#name
1: input#description
2: textarea#task
3: button
length: 4
description: input#description
name: input#name
tak: textarea#task
task: textarea#task

每个表单字段在 elements 集合中的顺序,与它们出现在标记中的顺序相同,可以按照位置和 name 特性来访问它们。现在,咱们有两种方法获取输入的值:

  • 通过类似数组的表示法: event.target.elements[0].value

  • 通过 id: event.target.elements.some_id.value

实际上,如果现在希望在每个表单元素上添加适当的id属性,则可以访问与event.target.elements.some_id 相同的元素,其中 id 是你分配给该属性的字符串。由于 event.target.elements 首先是一个对象,所以还可以使用 ES6 对象解构:

const { name, description, task } = event.target.elements;

这种做法不是 100% 推荐的,例如在 TypeScript 你会得到一个错误,但只要写 “vanilla JS” 就可以了。现在有了这些值,咱们就可以完成 handleSubmit了,在此过程中,还创建了另一个名为 saveData 的方法。现在它只是将值打印到控制台:

640?wx_fmt=png

这种保存数据的方式并不是最好的判断。如果字段更改怎么办?现在咱们有了 nametask  description,但将来可能会添加更多输入,所以需要动态提取这些字段。当然,还要解决对象销毁问题,来看看 event.target.elements

0: input#name
1: input#description
2: textarea#task
3: button
length: 4
description: input#description
name: input#name
tak: textarea#task
task: textarea#task

它看起来像一个数组。咱们使用 map 方法将其转换为仅包含 namedescriptiontask (过滤按钮类型 submit):

640?wx_fmt=png

在浏览器中尝试一下并查看控制台:

Uncaught TypeError: event.target.elements.map is not a function
    at HTMLFormElement.handleSubmit (form.js:15)

“ .map不是函数”。那么 event.target.elements 到底是什么?看起来像一个数组,但却是另一种野兽:它是 HTMLFormControlsCollection。在 第8章中,咱们对这些内容有所了解,并看到一些 DOM 方法返回了 HTMLCollection

// Returns an HTMLCollection
document.chidren;

HTML 集合看起来类似于数组,但是它们缺少诸如 map  filter 之类的用于迭代其元素的方法。仍然可以使用方括号表示法访问每个元素,我们可以通过 Array.from 将类似数组转成真正的数组:

640?wx_fmt=png

通过 Array.from 方法将 event.target.elements构造一个数组。Array.from 接受一个映射函数作为第二个参数,进一步优化:

640?wx_fmt=png

刷新 form.html,填写表单,然后按“提交”。在控制台中看到以下数组:

["Valentino", "Trip to Spoleto",	
 "We're going to visit the city!", undefined]

最后,我想生成一个对象数组,其中每个对象还具有相关表单输入的name属性:

640?wx_fmt=png

再次刷新 form.html,填写表单,将看到:

[
  {
    "name": "name",
    "value": "Valentino"
  },
  {
    "name": "description",
    "value": "Trip to Spoleto"
  },
  {
    "name": "task",
    "value": "We're going to visit the city!"
  },
  undefined
]

good job,有一个 undefined 的空值,它来自 button 元素。map 的默认行为是在“空”值的情况下返回 undefined。由于我们检查了 if (formInput.type !== "submit"),因此 button 元素未从 map 返回,而是被 undefined 取代。我们可以稍后将其删除,现在来看看 localStorage

了解 localStorage 并完善类

咱们有时候需要为用户保留一些数据,这样做有很多原因。例如考虑一个笔记应用程序,用户可以在 HTML表单中插入新内容,然后再回来查看这些笔记。下次她打开页面时,将在其中找到所有内容。

在浏览器中保存数据有哪些选项? 持久化数据的一种重要方法是使用数据库,但这里我们只有一些 HTML、JS 和浏览器。然而,在现代浏览器中有一个内置的工具,它就像一个非常简单的数据库,非常适合我们的需要:localStoragelocalStorage 的行为类似于 JS 对象,它有一堆方法:

  • setItem 用于保存数据

  • getItem 用于读取数据

  • clear 用于删除所有值

  • removeItem 用于清除对应的 key 的值

稍后我们将看到 setItem  getItem,首先咱们先得有一个 form.html 文件,内容如下:

640?wx_fmt=png

还有用于拦截提交事件的相关 JS 代码:

640?wx_fmt=png

此时,咱们需要实现 this.saveData 来将每个笔记保存到 localStorage。这样做时,需要保持尽可能的通用。换句话说,我不想用直接保存到 localStorage 的逻辑来填充this.saveData

相反,咱们为 Form 类提供一个外部依赖项(另一个类),该类的作用是实现实际代码。将来我们将这些笔记信息保存到 localStorage 还是数据库中都没有关系。对于每种用例,我们应该能够为 Form 提供不同的“存储”,并随着需求的变化而从一种转换为另一种。为此,我们首先调整构造函数以接受新的“存储”参数:

640?wx_fmt=png

现在,随着类的复杂度增加,需要验证构造函数的参数。作为一个用于处理 HTML 表单的类,咱们至少需要检查 formSelector 是否是 form 类型的 HTML 元素:

  constructor(formSelector, storage) {
    // Validating the arguments
    if (!(formSelector instanceof HTMLFormElement))
      throw Error(`Expected a form element got ${formSelector}`);
    //
    this.formSelector = formSelector;
    this.storage = storage;
    this.init();
  }

如果 formSelector 不是一个表单类型的,就会报错。另外还要验证 storage,因为我们必须将用户输入存储到某个地方。

  constructor(formSelector, storage) {
    // Validating the arguments
    if (!(formSelector instanceof HTMLFormElement))
      throw Error(`Expected a form element got ${formSelector}`);
    // Validating the arguments
    if (!storage) throw Error(`Expected a storage, got ${storage}`);
    //
    this.formSelector = formSelector;
    this.storage = storage;
    this.init();
  }

存储实现将是另一个类。在我们的例子中,可以是类似于通用LocalStorage的东西,在 form.js 中创建类 LocalStorage 

class LocalStorage {
  save() {
    return "saveStuff";
  }

  get() {
    return "getStuff";
  }
}

现在,有了这个结构,我们就可以连接 Form  LocalStorage

  • Form 中的 saveData 应该调用 Storage 实现

  • LocalStorage.save  LocalStorage.get 可以是静态的

仍然在 form.js 中,如下更改类方法:

640?wx_fmt=png

结尾彩蛋

欢迎关注前端之阶公众号,获取完整前端学习路线与资源,加入前端大群,与知名互联网大佬做朋友,开启共同学习新篇章!

640?wx_fmt=other

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JavaScript中的LocalStorage和SessionStorage都是用来在浏览器中存储数据的API,但是它们之间有一些区别。 LocalStorage是一种持久化存储数据的方式,数据存储在用户的本地浏览器中,并且在浏览器关闭后仍然可以访问。LocalStorage是基于域名的,即同一域名下的网页可以共享LocalStorage中存储的数据。 SessionStorage也是一种浏览器中存储数据的API,但是它的生命周期是在用户关闭当前浏览器窗口或者标签页之后就会自动销毁。SessionStorage也是基于域名的,即同一域名下的网页可以共享SessionStorage中存储的数据。 下面是一些LocalStorage和SessionStorage的使用示例: 1. 存储数据到LocalStorage中: ```javascript localStorage.setItem('key', 'value'); ``` 2. 从LocalStorage中获取数据: ```javascript var value = localStorage.getItem('key'); ``` 3. 删除LocalStorage中的数据: ```javascript localStorage.removeItem('key'); ``` 4. 将所有的LocalStorage数据清除: ```javascript localStorage.clear(); ``` 5. 存储数据到SessionStorage中: ```javascript sessionStorage.setItem('key', 'value'); ``` 6. 从SessionStorage中获取数据: ```javascript var value = sessionStorage.getItem('key'); ``` 7. 删除SessionStorage中的数据: ```javascript sessionStorage.removeItem('key'); ``` 8. 将所有的SessionStorage数据清除: ```javascript sessionStorage.clear(); ``` 需要注意的是,LocalStorage和SessionStorage都只能存储字符串类型的数据。如果要存储其他类型的数据,需要进行类型转换。同时,由于LocalStorage和SessionStorage的存储空间有限,如果存储的数据过多,可能会导致存储失败。因此,在使用LocalStorage和SessionStorage时需要注意存储的数据大小。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值