做者: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次左右,挺苦的,還望支持,給點鼓勵