做者:valentinogagliardi
译者:前端小智
来源:github
阿里云服务器很便宜火爆,今年比去年便宜,10.24~11.11购买是1年86元,3年229元,能够点击 下面连接进行参与:
https://www.aliyun.com/1111/2...javascript
为了保证的可读性,本文采用意译而非直译。html
网页不只仅是用来显示数据的。有了 HTML 表单,我们能够收集和操做用户数据。在本章中,经过构建一个简单的 HTML 表单来学习表单的相关的知识。前端
在这个过程当中,会了解更多关于 DOM 事件的信息,从在 第8章
咱们知道了一个 <form>
元素是一个 HTML 元素,它可能包含其余的子元素,好比:java
<input>
用于捕获数据<textarea>
用于捕获文本<button>
用于提交表单在本章中,我们构建一个包含 <input>
、<textarea>
和 <button>
的表彰。理想状况下,每一个 input
都应该具备 type
的属性,该属性指示输入类型: 例如 text
、email
、number
、date
等。除了 type
属性以外,可能还但愿向每一个表单元素添加 id
属性。git
input
和 textarea
也能够有一个 name
属性。若是大家想在不使用 JS 的状况下发送表单,name 属性很是重要。稍后会详细介绍。github
另外,将每一个表单元素与 <label>
关联也是一种常见的方式。在下面的示例中,会看到每一个 label
与 for
属性绑定对应 input
元素的 id
,做用是点击 label
元素就能让 input
聚焦。web
若是没有填写全部须要的信息,用户将没法提交表单。这是一个避免空数据的简单验证,从而防止用户跳太重要字段。有了这些知识,如今就能够建立 HTML 表单了。建立一个名为 form.html 的新文件并构建 HTML:redis
<html lang="en"> <head> <meta charset="UTF-8"> <title>HTML forms and JavaScript</title> </head> <body> <h1>What's next?</h1> <form> <label for="name">Name</label> <input type="text" id="name" name="name" required> <label for="description">Short description</label> <input type="text" id="description" name="description" required> <label for="task">Task</label> <textarea id="task" name="tak" required></textarea> <button type="submit">Submit</button> </form> </body> <script src="form.js"></script> </html>
如上所述,表单中的 input
具备正确的属性,从如今开始,能够经过填充一些数据来测试表单。 编写 HTML 表单时,要特别注意 type
属性,由于它决定了用户可以输入什么样的数据。数据库
HTML5 还引入了表单验证:例如,类型为 email
的输入只接受带有“at”
符 号@
的电子邮件地址。不幸的是,这是对电子邮件地址应用的唯一检查:没有人会阻止用户输入相似 a@a
这样的电子邮件。它有 @
,但仍然是无效的(用于电子邮件输入的 pattern
属性能够帮助解决这个问题。segmentfault
在 <input>
上有不少可用的属性,我发现 minlength
和 maxlength
是最有用的两个。在实战中,它们能够阻止懒惰的垃圾邮件发送者发送带有 “aa”
或 “testtest”
的表单。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>HTML forms and JavaScript</title> </head> <body> <h1>What's next?</h1> <form> <label for="name">Name</label> <input type="text" id="name" name="name" required minlength="5"> <label for="description">Short description</label> <input type="text" id="description" name="description" required minlength="5"> <label for="task">Task</label> <textarea id="task" name="tak" required minlength="10"></textarea> <button type="submit">Submit</button> </form> </body> <script src="form.js"></script> </html>
有了这个表单,我们就能够更进一步了,接着,来看下表单是如何工做的。
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 事件有不少类型,好比 click
、blur
、change
等等。如今,我们感兴趣的是 HTML 表单特有的 submit
事件。当用户单击 input
或 type
为 “submit” 的按钮(元素必须出如今表单中)时,就会分派 submit
事件,以下所示:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>HTML forms and JavaScript</title> </head> <body> <h1>What's next?</h1> <form> <label for="name">Name</label> <input type="text" id="name" name="name" required minlength="5"> <label for="description">Short description</label> <input type="text" id="description" name="description" required minlength="5"> <label for="task">Task</label> <textarea id="task" name="task" required minlength="10"></textarea> <button type="submit">Submit</button> </form> </body> <script src="form.js"></script> </html>
请注意,<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 的新文件,并从一个简单的类开始:
"use strict"; class Form { constructor(formSelector) { this.formSelector = formSelector; this.init(); } init() { this.formSelector.addEventListener("submit", this.handleSubmit); } }
我们的事件监听器是 this.handleSubmit
,与每一个事件监听器同样,它能够访问名为 event
的参数。 从第8章中应该知道,事件是实际分派的事件,其中包含有关动做自己的许多有用信息。 我们来实现 this.handleSubmit
:
"use strict"; class Form { constructor(formSelector) { this.formSelector = formSelector; this.init(); } init() { this.formSelector.addEventListener("submit", this.handleSubmit); } handleSubmit(event) { console.log(event); } }
而后,实例化类 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的 状况下发送表单的方式,由于它是基于 Django、Rails和friends 等 web 框架的应用程序的一部分。
每一个输入值都映射到相应的 name
属性。在本例中不须要 name
,由于这里我们想用 JS 来控制表单,因此须要禁用默认行为。能够经过调用 preventDefault
来禁用:
"use strict"; class Form { constructor(formSelector) { this.formSelector = formSelector; this.init(); } init() { this.formSelector.addEventListener("submit", this.handleSubmit); } handleSubmit(event) { event.preventDefault(); console.log(event); } } const formSelector = document.forms[0]; new Form(formSelector);
保存文件,而后再次刷新 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 表单与全部输入一块儿保存在那里,来看看是否确实如此。
为了获取表单的值,经过检查 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
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
的方法。如今它只是将值打印到控制台:
"use strict"; class Form { constructor(formSelector) { this.formSelector = formSelector; this.init(); } init() { this.formSelector.addEventListener("submit", this.handleSubmit); } handleSubmit(event) { event.preventDefault(); const { name, description, task } = event.target.elements; this.saveData({ name: name.value, description: description.value, task: task.value }); } saveData(payload) { console.log(payload); } } const formSelector = document.forms[0]; new Form(formSelector);
这种保存数据的方式并非最好的判断。 若是字段更改怎么办? 如今我们有了 name
,task
和 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
方法将其转换为仅包含 name
,description
和task
(过滤按钮类型 submit):
handleSubmit(event) { event.preventDefault(); const inputList = event.target.elements.map(function(formInput) { if (formInput.type !== "submit") { return formInput.value; } }); /* TODO this.saveData( maybe inputList ?) */ }
在浏览器中尝试一下并查看控制台:
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 将相似数组转成真正的数组:
handleSubmit(event) { event.preventDefault(); const arrOfElements = Array.from(event.target.elements); const inputList = arrOfElements.map(function(formInput) { if (formInput.type !== "submit") { return formInput.value; } }); console.log(inputList); /* TODO this.saveData( maybe inputList ?) */ }
经过 Array.from
方法将 event.target.elements
构造一个数组。Array.from
接受一个映射函数做为第二个参数,进一步优化:
handleSubmit(event) { event.preventDefault(); const inputList = Array.from(event.target.elements, function(formInput) { if (formInput.type !== "submit") { return formInput.value; } }); console.log(inputList); /* TODO this.saveData( maybe inputList ?) */ }
刷新 form.html,填写表单,而后按“提交”。 在控制台中看到如下数组:
["Valentino", "Trip to Spoleto", "We're going to visit the city!", undefined]
最后,我想生成一个对象数组,其中每一个对象还具备相关表单输入的name属性:
handleSubmit(event) { event.preventDefault(); const inputList = Array.from(event.target.elements, function(formInput) { if (formInput.type !== "submit") { return { name: formInput.name, value: formInput.value }; } }); console.log(inputList); /* TODO this.saveData( maybe inputList ?) */ }
再次刷新 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
。
我们有时候须要为用户保留一些数据,这样作有不少缘由。 例如考虑一个笔记应用程序,用户能够在 HTML表单中插入新内容,而后再回来查看这些笔记。 下次她打开页面时,将在其中找到全部内容。
在浏览器中保存数据有哪些选项? 持久化数据的一种重要方法是使用数据库,但这里咱们只有一些 HTML、JS 和浏览器。然而,在现代浏览器中有一个内置的工具,它就像一个很是简单的数据库,很是适合咱们的须要:localStorage
。localStorage
的行为相似于 JS 对象,它有一堆方法:
setItem
用于保存数据getItem
用于读取数据clear
用于删除全部值key
的值稍后咱们将看到 setItem
和 getItem
,首先我们先得有一个 form.html 文件,内容以下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>HTML forms and JavaScript</title> </head> <body> <h1>What's next?</h1> <form> <label for="name">Name</label> <input type="text" id="name" name="name" required minlength="5"> <label for="description">Short description</label> <input type="text" id="description" name="description" required minlength="5"> <label for="task">Task</label> <textarea id="task" name="task" required minlength="10"></textarea> <button type="submit">Submit</button> </form> </body> <script src="form.js"></script> </html>
还有用于拦截提交事件的相关 JS 代码:
"use strict"; class Form { constructor(formSelector) { this.formSelector = formSelector; this.init(); } init() { this.formSelector.addEventListener("submit", this.handleSubmit); } handleSubmit(event) { event.preventDefault(); const inputList = Array.from(event.target.elements, function(formInput) { if (formInput.type !== "submit") { return { name: formInput.name, value: formInput.value }; } }); console.log(inputList); /* TODO this.saveData( maybe inputList ?) */ } saveData(payload) { console.log(payload); } } const formSelector = document.forms[0]; new Form(formSelector);
此时,我们须要实现 this.saveData
来将每一个笔记保存到 localStorage
。 这样作时,须要保持尽量的通用。 换句话说,我不想用直接保存到 localStorage
的逻辑来填充this.saveData
。
相反,我们为 Form
类提供一个外部依赖项(另外一个类),该类的做用是实现实际代码。 未来咱们将这些笔记信息保存到 localStorage
仍是数据库中都没有关系。 对于每种用例,咱们应该可以为 Form
提供不一样的“存储”,并随着需求的变化而从一种转换为另外一种。 为此,咱们首先调整构造函数以接受新的“存储”参数:
class Form { constructor(formSelector, storage) { this.formSelector = formSelector; this.storage = storage; this.init(); } init() { this.formSelector.addEventListener("submit", this.handleSubmit); } handleSubmit(event) { event.preventDefault(); const inputList = Array.from(event.target.elements, function(formInput) { if (formInput.type !== "submit") { return { name: formInput.name, value: formInput.value }; } }); } saveData(payload) { console.log(payload); } }
如今,随着类的复杂度增长,须要验证构造函数的参数。做为一个用于处理 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 中,以下更改类方法:
"use strict"; /* Form implementation */ class Form { constructor(formSelector, storage) { // Validating the arguments if (!(formSelector instanceof HTMLFormElement)) throw Error(`Expected a form element got ${formSelector}`); // Validating the arguments if (!(storage instanceof Storage)) throw Error(`Expected a storage, got ${storage}`); // this.formSelector = formSelector; this.storage = storage; this.init(); } init() { this.formSelector.addEventListener("submit", this.handleSubmit); } handleSubmit(event) { event.preventDefault(); const inputList = Array.from(event.target.elements, function(formInput) { if (formInput.type !== "submit") { return { name: formInput.name, value: formInput.value }; } }); this.saveData('inputList', inputList); } saveData(key,payload) { this.storage.save(key, payload); } } /* Storage implementation */ class LocalStorage { static save(key, val) { if (typeof val === 'object') { val = JSON.stringify(val) } localStorage.setItem(key, val, redis.print) } static get(key) { const val = localStorage.getItem(key) if (val === null) return null return JSON.parse(val) } } const formSelector = document.forms[0]; const storage = LocalStorage; new Form(formSelector, storage);
代码部署后可能存在的BUG无法实时知道,过后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给你们推荐一个好用的BUG监控工具 Fundebug。
原文:https://github.com/valentinog...
阿里云最近在作活动,低至2折,有兴趣能够看看:https://promotion.aliyun.com/...
干货系列文章汇总以下,以为不错点个Star,欢迎 加群 互相学习。
https://github.com/qq449245884/xiaozhi
由于篇幅的限制,今天的分享只到这里。若是你们想了解更多的内容的话,能够去扫一扫每篇文章最下面的二维码,而后关注我们的微信公众号,了解更多的资讯和有价值的内容。
每次整理文章,通常都到2点才睡觉,一周4次左右,挺苦的,还望支持,给点鼓励